Monorepo WhatsApp assistant using Hexagonal architecture.
cp .env.example .env
npm install
npm run prisma:generateLLM_ENABLED=falseskips the LLM fallback and keeps commands/triggers active.BOT_TIMEZONE(defaultAmerica/Cuiaba) controls all reminder parsing/formatting.BOT_PREFIX(default/) is the global command prefix; parsing/help text respect this value.ADMIN_API_BASE_URLseeds Admin UI default control-plane target (defaulthttp://localhost:3333).- Optional
ASSISTANT_API_BASE_URLallowsadmin-apistatus aggregation to report assistant-api health if that runtime is still enabled. - Logging env overrides:
LOG_FORMAT,LOG_LEVEL,LOG_PRETTY_MODE,LOG_COLORIZE,LOG_VERBOSE_FIELDS(runtime mode still applies sane defaults). - Replay/backlog guard is multi-layered: startup watermark + dedupe claim (
remoteJid + waMessageId) + stale age guard. Main knobs:INBOUND_MAX_MESSAGE_AGE_SECONDS(default30),INBOUND_STARTUP_WATERMARK_TOLERANCE_SECONDS(default5),INBOUND_MISSING_TIMESTAMP_STARTUP_GRACE_SECONDS(default15),INBOUND_MESSAGE_CLAIM_TTL_SECONDS(default172800). STICKER_MAX_VIDEO_SECONDS(default10) limits short-video sticker generation; videos above this threshold are rejected with friendly feedback.- Operational reactions are configurable via
WA_REACTIONS_ENABLED,WA_REACTION_PROGRESS,WA_REACTION_SUCCESS,WA_REACTION_FAILURE(defaults:⏱️,✅,❌). - Audio STT-first capability is controlled by
AUDIO_CAPABILITY_ENABLED,AUDIO_AUTO_TRANSCRIBE_ENABLED,AUDIO_STT_MODEL,AUDIO_STT_TIMEOUT_MS,AUDIO_MAX_DURATION_SECONDS,AUDIO_MAX_BYTES,AUDIO_STT_LANGUAGE. - Audio dynamic command dispatch is controlled by
AUDIO_COMMAND_DISPATCH_ENABLED,AUDIO_COMMAND_ALLOWLIST,AUDIO_COMMAND_MIN_CONFIDENCE,AUDIO_TRANSCRIPT_PREVIEW_CHARS. - TTS is controlled by
TTS_ENABLED,TTS_MODEL,TTS_TIMEOUT_MS,TTS_AUDIO_FORMAT,TTS_TRANSLATION_MODEL,TTS_TRANSLATION_TIMEOUT_MS,TTS_DEFAULT_SOURCE_LANGUAGE,TTS_DEFAULT_LANGUAGE,TTS_DEFAULT_VOICE,TTS_MALE_VOICE,TTS_FEMALE_VOICE,TTS_MAX_TEXT_CHARS,TTS_SEND_AS_PTT. - Web search is controlled by
SEARCH_ENABLED,SEARCH_PROVIDER,SEARCH_MAX_RESULTS,SEARCH_TIMEOUT_MS,GOOGLE_SEARCH_API_KEY,GOOGLE_SEARCH_ENGINE_ID(GOOGLE_SEARCH_CXremains supported for backward compatibility). - AI-assisted web search is controlled by
SEARCH_AI_ENABLED,SEARCH_AI_PROVIDER,SEARCH_AI_MODEL,SEARCH_AI_TIMEOUT_MS,SEARCH_AI_MAX_SOURCES,GEMINI_API_KEY,GEMINI_SEARCH_AI_MODEL,GEMINI_SEARCH_GROUNDING_ENABLED. - Image search is controlled by
IMAGE_SEARCH_ENABLED,IMAGE_SEARCH_PROVIDER,IMAGE_SEARCH_MAX_RESULTS, provider credentials (PIXABAY_API_KEY,PEXELS_API_KEY,UNSPLASH_ACCESS_KEY), optionalOPENVERSE_API_BASE_URL, and normalization knobs (IMAGE_SEARCH_MEDIA_NORMALIZE_*). - Downloads module is controlled by
DOWNLOADS_MODULE_ENABLEDand delegated to the internal resolver API viaMEDIA_RESOLVER_API_BASE_URL+MEDIA_RESOLVER_API_TOKEN. - Media resolver runtime knobs:
MEDIA_RESOLVER_API_PORT,MEDIA_RESOLVER_JOB_TTL_SECONDS,MEDIA_RESOLVER_CLEANUP_INTERVAL_MS,MEDIA_RESOLVER_TEMP_DIR,MEDIA_RESOLVER_TEMP_RETENTION_SECONDS. - Downloads provider/router knobs:
DOWNLOADS_MAX_BYTES,DOWNLOADS_PROVIDER_YT_ENABLED,DOWNLOADS_PROVIDER_IG_ENABLED,DOWNLOADS_PROVIDER_FB_ENABLED,DOWNLOADS_PROVIDER_DIRECT_ENABLED,DOWNLOADS_DIRECT_TIMEOUT_MS. - External bridge knobs (internal auxiliary services):
YT_RESOLVER_ENABLED,YT_RESOLVER_BASE_URL,YT_RESOLVER_TOKEN,YT_RESOLVER_TIMEOUT_MS,YT_RESOLVER_MAX_BYTES,FB_RESOLVER_ENABLED,FB_RESOLVER_BASE_URL,FB_RESOLVER_TOKEN,FB_RESOLVER_TIMEOUT_MS,FB_RESOLVER_MAX_BYTES. - Optional official metadata/auth hints (probe enrichment only):
YOUTUBE_API_KEY,FACEBOOK_ACCESS_TOKEN,FACEBOOK_GRAPH_API_VERSION. - Internal worker -> gateway delivery uses
WA_GATEWAY_INTERNAL_BASE_URL,WA_GATEWAY_INTERNAL_PORT, andWA_GATEWAY_INTERNAL_TOKEN. - Internal worker -> gateway dispatch responses use
sendStatusto confirm WA send outcome; HTTP200alone means the dispatch request was accepted. - Consent config:
CONSENT_TERMS_VERSION,CONSENT_LINK,CONSENT_SOURCEdrive the onboarding/legal prompt for common users. - Governance runtime controls:
GOVERNANCE_ENFORCEMENT_ENABLED(defaulttrue) applies runtime allow/deny decisions for scoped governance capabilities.GOVERNANCE_SHADOW_MODE(defaulttrue) keeps shadow telemetry logs enabled for observability.GOVERNANCE_FREE_DIRECT_CHAT_LIMIT(default30) defines the initial FREE plan daily direct-chat quota used by governance enforcement.
Bootstrap once per host (recommended):
npm run bootstrap:dev -- --infra
# or
npm run bootstrap:prod -- --infrabootstrap is a thin orchestrator: it only selects resolver modules and delegates to each module scripts/bootstrap.sh with module-local cwd. It is not part of recurring start.
All start modes (dev, prod, debug) support:
--infra=external--infra=managed--infra=auto(default)
Semantics:
external: never runsdocker compose upforredis/postgres; validates configured endpoints only.managed: requires compose-managedredis/postgres; may start only those managed dependencies.auto: discovers configured endpoints first and uses existing usable services; only starts managed dependency when no usable service is found.
Dependency discovery before any compose-up includes:
- TCP probe
- Redis
PING - PostgreSQL real connection check (
SELECT 1) - ownership/source classification:
external_host,external_container,compose_managed,unavailable
- PostgreSQL:
5432 - Redis:
6379 admin-api:3333(ADMIN_API_PORT)wa-gatewayinternal endpoint:3334(WA_GATEWAY_INTERNAL_PORT)media-resolver-api:3335(MEDIA_RESOLVER_API_PORT)admin-ui:8080(ADMIN_UI_PORT)youtube-resolver:3401(YT_RESOLVER_BASE_URL)facebook-resolver:3402(FB_RESOLVER_BASE_URL)
Preferred local command:
npm run start:dev -- --infra=autoRoot app startup behavior now includes pre-start reconciliation for admin-ui (8080), admin-api (3333), wa-gateway (3334), and media-resolver-api (3335):
[app-precheck] ... status=ready_to_start-> service spawn continues.[app-precheck] ... status=already_running_same_service-> duplicate spawn is skipped ([app-start-skip] ...).[app-precheck] ... status=port_conflict_unknown_process-> startup fails clearly (no duplicate spawn).
Production:
npm run start:prodBuild-on-demand:
npm run start:prod -- --buildDebug:
npm run start:debugProduction host example (native Redis already on 6379):
npm run start:prod -- --infra=autoExpected behavior: dependency source becomes external_host and compose autostart for Redis is skipped (no port conflict).
Resolver startup via delegation:
npm run start:dev -- --infra=auto --with-external-services
# or per service:
npm run start:dev -- --with-yt-resolver
npm run start:dev -- --with-fb-resolverResolver runtime behavior:
- root start validates selection + resolver health and then delegates to module runtime script (
cd <module-dir> && bash scripts/run.sh) only when needed - if resolver health is already OK, startup logs
health_ok_already_runningand skips duplicate startup - root start does not create
.venv, install dependencies, or activate Python inline - resolver internals (tmux/screen/backgrounding/uvicorn/etc.) are module-owned and must stay inside each module
scripts/run.sh
Stop app services only:
npm run stop:devnpm run stop:prodnpm run stop:debugOptional cleanup mode for stale Zappy leftovers on root app ports (8080/3333/3334/3335):
npm run stop:dev -- --cleanup-ports
# alias:
npm run stop:dev -- --force-runtime-cleanupStop services plus infra/resolver resources owned by this runtime:
npm run stop:dev -- --infranpm run stop:prod -- --infranpm run stop:debug -- --infraOwnership-aware stop rules with --infra:
external_hostandexternal_containerdependencies are never stopped.- only
compose_manageddependencies started by current runtime are candidates for stop. - resolver stop is delegated only when state marks module as
runtime_delegatedand module providesscripts/stop.sh. - if
scripts/stop.shis missing, stop logsmissing_stop_scriptand reports manual/non-delegated module stop. - after PID-based stop, root ports
8080/3333/3334/3335are reconciled with[stop-reconcile]statuses:stopped_by_pidalready_stoppedport_still_busy_unknown_process(reported only; unknown processes are not force-killed)
Cleanup behavior with --cleanup-ports:
- default stop remains unchanged/safe (
npm run stop:*without cleanup is report-only for unknown owners). - cleanup scans root app ports and classifies listeners by Zappy command/path markers.
- only confidently-classified Zappy leftovers receive signal sequence
SIGINT -> SIGTERM -> SIGKILL(last resort). - cleanup fallback can classify
zappy_likely_process_by_port_and_runtimewhenknown_port,node_like,runtime_markersignals match. - non-Zappy/uncertain listeners are skipped and logged as
skipped_non_zappy_process. - owner logs include
matchedSignals=...so kill/skip reasoning is explicit.
npm run restart:dev
npm run restart:dev -- --infra=auto --with-external-services
npm run restart:prod -- --infra=managed --build
npm run restart:debugrestart forwards infra strategy to start and can stop owned infra when --infra (or --infra=<mode>) is provided.
- Pure external deps:
npm run start:prod -- --infra=externalUse host-managed/external Redis/Postgres, no compose-managed infra. - Managed deps:
npm run start:prod -- --infra=managedRedis/Postgres owned by project compose. - Hybrid:
npm run start:prod -- --infra=autoReuse existing native/external services first and compose-up only missing dependencies.
If you still prefer the old behavior, npm run dev remains available (it prints each service banner).
Manual steps that remain:
- keep
.envup to date and runnpm run prisma:migratewhen schema changes - WhatsApp pairing (see below) still requires manual code entry
- Source location:
infra/external-services/youtube-resolverandinfra/external-services/facebook-resolver. - Initialize vendored sources (if needed):
git submodule update --init --recursive infra/external-services/youtube-resolver infra/external-services/facebook-resolver. - Bootstrap Python envs (one-time host setup):
npm run bootstrap:dev -- --infranpm run bootstrap:prod -- --infra
- Root bootstrap does not run
pip/venvdirectly; each resolver owns its bootstrap internals inscripts/bootstrap.sh. - Start wrappers manually:
./infra/external-services/youtube-resolver/scripts/run.sh./infra/external-services/facebook-resolver/scripts/run.sh
- Start via root delegation:
- all enabled bridges:
npm run start:dev -- --infra=auto --with-external-services - only YouTube wrapper:
npm run start:dev -- --with-yt-resolver - only Facebook wrapper:
npm run start:dev -- --with-fb-resolver
- all enabled bridges:
- Start-time duplicate guard:
- if resolver is already healthy, startup logs
health_ok_already_runningand does not spawn duplicate startup.
- if resolver is already healthy, startup logs
- Root start does not activate
.venvor runuvicorninline; it delegates to modulescripts/run.shusing resolver-localcwd. - Stop-time ownership guard:
npm run stop:dev -- --infradelegates toscripts/stop.shonly when module provides it.- without module
scripts/stop.sh, stop reports manual action required.
- Health endpoints:
- YouTube:
GET http://localhost:3401/health - Facebook:
GET http://localhost:3402/health
- YouTube:
- Required host dependency for Facebook resolver flows:
ffmpeg. - Full runbook:
docs/external-resolver-services.md.
Operator guide (lifecycle + Redis strategy): docs/runtime-lifecycle-operator-guide.md.
disabled_by_env:- resolver was requested by
--with-external-servicesbutYT_RESOLVER_ENABLED/FB_RESOLVER_ENABLEDisfalse.
- resolver was requested by
EADDRINUSE:- app port conflict. Check
[app-precheck]status first. already_running_same_service: duplicate spawn is skipped automatically.port_conflict_unknown_process: free the conflicting port or change service port env (ADMIN_API_PORT,ADMIN_UI_PORT,WA_GATEWAY_INTERNAL_PORT,MEDIA_RESOLVER_API_PORT, resolver base URL ports3401/3402).
- app port conflict. Check
- stale/missing state file (
.zappy-dev/<mode>-stack.json):- startup/stop logs explicit
[state] status=missing|stale_*|activediagnostics. - stale state is cleared automatically before continuing.
- startup/stop logs explicit
- stale Zappy process still holding root app port after stop:
- run explicit cleanup mode:
npm run stop:dev -- --cleanup-ports. - check
[cleanup]logs forclassification=...,signal_sent, and finalstatus=cleared|still_busy. - non-Zappy processes are intentionally not killed (
status=skipped_non_zappy_process).
- run explicit cleanup mode:
- external Redis warning (
min_version_recommended):- check
[deps] service=redis source=... version=.... - recommendation: use compose-managed Redis 7, or upgrade host Redis to
>= 6.2.0.
- check
- missing exports / package errors:
- startup logs
category=package_export_error; check recent package changes and runnpm install+npm run build/npm run prisma:generateas needed.
- startup logs
- resolver health fails after delegation:
- root logs
health_fail_after_delegate:*; inspect the module directly by running itsscripts/run.shand module logs.
- root logs
Version note: current release line is v1.9.0.
- Set
WA_PAIRING_PHONEwith country code (e.g.5511999999999) and start gateway. - Gateway logs a pairing code (
pairing code) for multi-device login. - In WhatsApp: Linked devices -> Link with phone number -> enter code.
- Session credentials persist in
WA_SESSION_PATH(default.wa_auth).
If ONLY_GROUP_ID is set, gateway processes only that group; otherwise it auto-registers groups/users under a default tenant.
- Core orchestrator pipeline: flags -> triggers -> commands -> LLM fallback.
- Governance runtime enforcement (Phase 2 evolution): persisted access (
PENDING/APPROVED/BLOCKED) + flexible capability policy (tier defaults -> bundle grants -> explicit overrides) + FREE direct-chat quota hook enforced in runtime for scoped capabilities, with structured decision logs and admin snapshot visibility. - Admin control-plane API (
apps/admin-api) now persists and exposes governance administrative state:- approvals:
/admin/v1/users/:waUserId/access,/admin/v1/groups/:waGroupId/access - licensing:
/admin/v1/licenses/plans,/admin/v1/users/:waUserId/license,/admin/v1/groups/:waGroupId/license - usage visibility:
/admin/v1/usage/users/:waUserId,/admin/v1/usage/groups/:waGroupId - approval audit trail:
/admin/v1/audit - capability catalogs:
/admin/v1/governance/capabilities,/admin/v1/governance/bundles - bundle catalog management:
POST /admin/v1/governance/bundles,PATCH /admin/v1/governance/bundles/:bundleKey - bundle capability composition:
PUT|DELETE /admin/v1/governance/bundles/:bundleKey/capabilities/:capabilityKey - effective policy resolution:
/admin/v1/governance/users/:waUserId/effective,/admin/v1/governance/groups/:waGroupId/effective - bundle assignment/removal:
PUT|DELETE /admin/v1/governance/users/:waUserId/bundles/:bundleKey,PUT|DELETE /admin/v1/governance/groups/:waGroupId/bundles/:bundleKey - capability override set/clear:
PUT|DELETE /admin/v1/governance/users/:waUserId/capabilities/:capabilityKey,PUT|DELETE /admin/v1/governance/groups/:waGroupId/capabilities/:capabilityKey - governance defaults/settings view:
GET /admin/v1/governance/settings - first-seen materialization defaults: private users
status=APPROVED,tier=FREE; groupsstatus=PENDING,tier=FREE
- approvals:
- Commands:
/help,/task add/list/done,/note add/list/rm,/agenda,/calc,/timer,/mute <duration|off>,/whoami,/status,/reminder in/at,/sticker(/s,/stk,/fig),/toimg,/rnfig,/transcribe(/tr,/tss),/tts,/trl,/search,/google,/search-ai(/sai),/img(/gimage),/imglink,/dl. - Stickers capability:
/stickergera figurinha a partir de imagem ou vídeo curto (resposta ou legenda), com ajustecontain(sem crop) e padding transparente./toimgfunciona apenas respondendo uma figurinha válida./rnfig Autor|Pacoteatualiza apenas metadados EXIF de autor/pacote ao responder uma figurinha.- Operações pesadas de sticker disparam reação de progresso (
⏱️) e conclusão (✅/❌) na mensagem de origem (best-effort). - Limitações atuais: vídeo curto apenas (limitado por
STICKER_MAX_VIDEO_SECONDS), sem gif avançado, sem editor visual, sem suporte a vídeos longos.
- Audio capability (STT-first):
/transcribe(alias/tss) transcreve áudio ao responder uma mensagem de áudio.- Áudio enviado diretamente ao bot pode ser transcrito e roteado para resposta no fluxo existente.
- Dispatch dinâmico por voz usa estratégia controlada: prefixo explícito (
/), comando faladoslash|barra <comando>, ou primeiro token da allowlist (AUDIO_COMMAND_ALLOWLIST) com confiança mínima (AUDIO_COMMAND_MIN_CONFIDENCE). - Em baixa confiança, o bot não executa comando dinâmico e responde com fallback amigável/transcrição curta.
- TTS module:
/tts <texto>usa origem/destino/voz padrão (TTS_DEFAULT_SOURCE_LANGUAGE,TTS_DEFAULT_LANGUAGE,TTS_DEFAULT_VOICE).- Formato compatível:
/tts <texto> |<destino>|<voz>(ex:/tts Bom dia |en|female). - Formato explícito origem->destino:
/tts <texto> |<origem>|<destino>|<voz>(ex:/tts Bom dia |pt-BR|en|female). - Também aceita uso por resposta: responda um texto e envie
/tts(ou/tts |<destino>|<voz>). - Quando origem e destino diferem, o texto é traduzido antes da síntese; se a tradução falhar, o áudio não é gerado.
- Saída padrão como voice note/PTT (
TTS_SEND_AS_PTT=true) com recodificação final paraOGG/Opusno gateway (mimetype finalaudio/ogg; codecs=opus). - Limitações: qualidade de tradução depende do provider/model configurado; textos muito longos respeitam
TTS_MAX_TEXT_CHARS; para PTT 100% compatível o host deve terffmpegdisponível (sem isso o envio de áudio é abortado com erro claro, sem fallback para áudio comum).
- Translation module:
/trl <texto>traduz com detecção automática de idioma e saída curta para WhatsApp.- Alvo padrão: origem
pt*->en; demais origens ->pt. - Override de alvo:
/trl <texto> |<destino>(ex:/trl Olá |en). - Também aceita uso por resposta: responda um texto e envie
/trl. - Resposta a áudio também é suportada: responda um áudio e envie
/trl(ou/trl |<destino>); o fluxo interno transcreve e depois traduz.
- Web search module:
/search <termo>executa busca textual genérica (provider preferido emSEARCH_PROVIDERcom fallback automático) e aceita termo via resposta./google <termo>usa Google Programmable Search real (sem cair silenciosamente no mesmo fluxo de/search) e aceita termo via resposta.- Quantidade de resultados controlada por
SEARCH_MAX_RESULTS. - Provider configurável (
SEARCH_PROVIDER) com fallback para DuckDuckGo no comando/search. /googledepende deGOOGLE_SEARCH_API_KEY+GOOGLE_SEARCH_ENGINE_ID(ouGOOGLE_SEARCH_CXlegado); se faltar configuração, retorna aviso amigável.- Limitações: sem síntese semântica de múltiplas fontes (para isso, use
/search-ai).
- AI-assisted web search module:
/search-ai <termo>e/sai <termo>executam busca assistida por IA com acesso à internet e aceitam termo via resposta.- Retorna resposta resumida + principais fontes/links.
- Provider selecionável por
SEARCH_AI_PROVIDER(openaiougemini). - Para
gemini, habiliteGEMINI_API_KEYe mantenhaGEMINI_SEARCH_GROUNDING_ENABLED=truepara grounding com Google Search. - Limitações: depende de modelo com suporte a web tool/grounding; falhas de quota/permissão do provider podem indisponibilizar o recurso.
- Image search module:
/img <termo>e/gimage <termo>priorizam resultado visual relevante, com variabilidade controlada para evitar repetição excessiva no mesmo termo./imglink <termo>usa a mesma busca e retorna fallback conciso em linha curta com link útil.- Ambos aceitam termo via resposta a mensagem de texto.
- Legenda/retorno segue formato curto: descrição breve (quando útil) + uma fonte.
- Quantidade de resultados controlada por
IMAGE_SEARCH_MAX_RESULTS. - Estratégia nativa-first: Wikimedia Commons -> Openverse -> Pixabay -> Pexels -> Unsplash; Google CSE entra apenas como fallback de descoberta.
- Política de qualidade de domínio: prioriza fontes confiáveis e exclui Pinterest/Behance/Dribbble/ArtStation/DeviantArt do pipeline
/img. - O adapter valida e normaliza mídia (resize/re-encode JPEG/PNG quando necessário) para melhorar entregabilidade no WhatsApp.
- Downloads module (
/dl) via internalmedia-resolver-api:wa-gatewayapenas delega resolução de mídia e envia o outbound normalizado.- Pipeline por provider:
detect -> probe -> resolveAsset -> download -> normalizeForWhatsApp. igreutiliza o fluxo staged já estável para links públicos.ytefbpodem operar via bridges HTTP internas (yt-resolver-service/fb-resolver-service) com normalização local para contratos Zappy (resultKind,status,asset).- payload bruto dos serviços externos não é exposto para o gateway: toda tradução fica no
media-resolver-api. - com bridge desativada (
YT_RESOLVER_ENABLED=false/FB_RESOLVER_ENABLED=false), o runtime usa fallback staged legado. - O resolver mantém retries internos, job TTL em Redis e cleanup periódico de arquivos temporários.
- Reminders:
/reminder in <duration> <message>where duration accepts1,10m,1h40m30s,2d./reminder at <DD-MM[-YYYY]> [HH:MM] <message>usesBOT_TIMEZONEand defaults time to08:00.
- Notes module (scoped to group or user) with public IDs like
N001and preview listing. - Agenda command returns today's tasks + reminders using
BOT_TIMEZONE. - Calculator uses a safe expression parser for
/calc <expression>. - Timer command schedules short-term timers via BullMQ (
fire-timerjobs). - Mute command stores a scoped mute window in Redis; triggers/LLM stay silent while muted.
- Status command aggregates gateway/worker heartbeats, DB/Redis checks, and counts for tasks/reminders/timers.
- Trigger priority, cooldown, template variables.
- Reminder and timer jobs via BullMQ with idempotent worker.
- Admin control-plane stack (
admin-api+admin-uiMVP):- Dashboard health/summary view for
wa-gateway,worker,admin-api,media-resolver-api, optionalassistant-api, Redis/Postgres, queue/reminders, and recent failures. - Users/Groups operations with approve, block, tier assignment, bundle assignment/removal, and per-capability override controls backed by persisted governance entities.
- Bundles management view with create/edit flows and capability composition per bundle.
- Capabilities catalog view with category/description and bundle membership visibility.
- Governance settings view exposing separated defaults for new private users vs groups.
- Licenses/Plans catalog with capability metadata visibility.
- Effective capabilities view for users/groups with source attribution (
tier_default,bundle,user_override_allow,group_override_allow) and deny-source diagnostics. - Audit history view with actor/subject/action/timestamp and before/after summaries where available.
- Jobs/Reminders view with failed reminder inspection and safe retry action.
- Dashboard health/summary view for
- Services.NET onboarding/consent gate for common users (SIM/NÃO) with link (
CONSENT_LINK), pending reminder state, and privileged bypass (creator_root, mother_privileged, owners/admins). - Relationship-aware personas with resolver:
- creator_root (
556699064658) and mother_privileged (556692283438) get tailored tone, initiative, and deeper memory; other profiles map to delegated_owner/admin/member/external_contact.
- creator_root (
- Natural-language tools: create/update/complete/delete tasks, create/update/delete reminders, add/list notes, get time/settings without slash commands.
- Interactive slot filling with stateful follow-ups and cancel/confirmation for destructive actions.
- Privileged memory windows (creator/mother keep larger short-term context) plus concise profile notes injected into prompts.
- Canonical identity tracks
waUserId, normalizedphoneNumber,pnJid(@s.whatsapp.net),lidJid(@lid),aliases[],displayName,permissionRole, andrelationshipProfile. - Resolution order:
phoneNumber→pnJid→lidJid→waUserId→aliases. Every inbound identifier is merged into the alias set to prevent future mismatches. - UX guardrail: AI addressing name resolves safely (
displayNameconfiável → friendly context name → fallbackvocê) and never uses internal role labels as vocative name (ROOT,creator_root,bot_admin, etc.). - Privileged mapping (by phone/pnJid/lidJid/aliases):
556699064658→creator_root+ permission roleROOT;556692283438→mother_privileged+ permission rolePRIVILEGED. - LID ids differ from phone numbers because WhatsApp obfuscates contact ids; add aliases when mapping a new LID to a known phone to keep profiles aligned.
- Admin/root command to bind aliases when WhatsApp hides the phone number:
/alias link <phoneNumber> <lidJid>(example:/alias link 556699064658 70029643092123@lid). The link is stored, relationshipProfile/permissionRole are recalculated immediately, and duplicate users are merged.
- Runtime defaults by mode:
dev: pretty + detailed (LOG_FORMAT=pretty,LOG_PRETTY_MODE=dev,LOG_COLORIZE=true,LOG_LEVEL=debug,LOG_VERBOSE_FIELDS=true)prod: pretty + operational one-line output (LOG_FORMAT=pretty,LOG_PRETTY_MODE=prod,LOG_COLORIZE=true,LOG_LEVEL=info,LOG_VERBOSE_FIELDS=false)debug: raw/json + max technical detail (LOG_FORMAT=json,LOG_LEVEL=debug,LOG_VERBOSE_FIELDS=true,DEBUG=trace)
- Configurable env knobs:
LOG_FORMAT=pretty|jsonLOG_LEVEL=fatal|error|warn|info|debug|trace|silentLOG_PRETTY_MODE=dev|prodLOG_COLORIZE=true|false(default follows mode/supervisor;NO_COLORdisables)LOG_VERBOSE_FIELDS=true|false
- Backward compatibility:
PRETTY_LOGS=falsestill forces JSON whenLOG_FORMATis not explicitly set. - Local timestamps respect
BOT_TIMEZONE.DEBUG=tracedisables Baileys noise filtering;DEBUG=stackshows stack traces in pretty WARN/ERROR blocks. - Pretty output keeps category-first lines (
SYSTEM,AUTH,WA-IN,WA-OUT,AI,HTTP,QUEUE,DB,WARN,ERROR,COMMAND_TRACE), includes source tag ([wa-gateway],[worker], etc.), and preserves key traceability fields (msg,in,exec,resp) when present. - In
prodpretty profile, each event is rendered as a clean single line. - Replay drops are explicit and auditable:
stale inbound skipped,replay/backlog inbound skipped(startup watermark), andduplicate inbound message skipped(dedupe claim hit).
npm run start:devprints a single cfonts banner (Zappy Assistant ©) with runtime metadata and runs all services in watch mode.npm run start:prodprints a compact runtime header (mode=prod,build=...) and starts services without watch mode (clean operational output, no dev branding per service).npm run start:debugprints a compact runtime header (mode=debug) and starts services without watch mode using raw/json logging for deep troubleshooting.- Running a service directly (
npm run dev -w ...) still shows the per-service startup banner with status hints (Redis/DB/Worker/LLM) and Admin URLs. Status transitions remain logged clearly: WhatsApp CONNECTING/QR READY/CONNECTED/DISCONNECTED, Redis/DB OK|FAIL, Worker OK|FAIL.
- API:
http://localhost:3333 - UI:
http://localhost:8080 - Use
Authorization: Bearer <ADMIN_API_TOKEN>for/admin/*.
packages/aicentralizes persona and prompt builder; OpenAI access stays in adapters.- Persona
secretary_default: Alan's digital secretary (friendly, polite, slightly formal, organized, concise, proactive, calm). - Prompt builder assembles identity, role, tone, operational policies (tool-first, no hallucination), context (scope + role + handoff), tools/modules, datetime/timezone, and output expectations. Memory is included only up to the provided limit.
- LLM is optional (
LLM_ENABLED=falsekeeps commands/triggers only). Seepackages/ai/README.mdfor examples of direct vs group prompts. - AI memory:
ConversationMemorystores trimmed, AI-relevant turns (default windowLLM_MEMORY_MESSAGES=10; creator_root uses 24, mother_privileged 18) separate from rawMessagelogs; older items are trimmed automatically. - AI responses:
AiTextReply,AiToolIntent(suggested tool actions: create/list task/reminder, add/list note, get_time, get_settings),AiFallback. Orchestrator receives tool suggestions but decides whether to execute or just reply. - Relationship-aware persona modifiers adjust tone/initiative/creativity based on profile; internal guardrails prevent leaking this framing to other users.
- Creator user (
556699064658) sends a message → recognized ascreator_root/ROOT(check/whoamiand logs for phone/profile). - Mother user (
556692283438) sends a message → recognized asmother_privilegedwith privileged tone. - Same person appears as LID then PN → add alias for the LID, subsequent messages resolve to the same profile/permissions (no duplicate users).
- Logs show normalized identity info: categories
WA-IN/WA-OUTwith phone + relationship profile and message preview. - LLM disabled:
LLM_ENABLED=false, ask a question → graceful fallback text. - LLM enabled: ask a general question → text reply.
- Intent: “Me lembre de pagar o boleto amanhã” → tool intent
create_remindersuggested. - Group chat: ask a short question → concise reply with context.
- Creator (556699064658): “Preciso de um plano para o lançamento.” → proactive, strategic outline + suggested next actions.
- Mother (556692283438): “Me lembra de tomar o remédio às 20h.” → gentle confirmation using soft address and schedules the reminder.
- Regular member: “cria uma tarefa para ligar para o cliente amanhã” → parses intent, asks for missing time if needed, then creates the task.