feat(discord-bridge): comenzi slash in loc de prefixul !
Comenzile devin application commands inregistrate pe guild (sync instantaneu, spre deosebire de cel global care dureaza ~1h): /new [fork], /cd <cale>, /model <sonnet|opus> cu Choice, /status, /stop, /cleanup [force], /help. - allowlist-ul se aplica identic la interactiuni (check_ids comun, ca sa nu existe a doua implementare care diverge); refuz efemer, fara executie - fiecare comanda face defer() inainte de lucru — altfel Discord marcheaza interactiunea esuata dupa 3s desi comanda a rulat - sync tolerant: la esec (lipsa scope applications.commands) botul porneste normal si logheaza linkul de reinvitare necesar - mesajele obisnuite raman neschimbate, inclusiv steering-ul mid-tur - linkul de invitatie primeste scope=bot%20applications.commands; referintele la ! din ops/ si documentatie trecute pe / Verificat in productie: 7 comenzi inregistrate pe guild, citite inapoi din API. Suita: 296 passed cu discord.py, 293 passed + 3 skipped fara. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
This commit is contained in:
@@ -29,7 +29,7 @@ bot.py -- allowlist (guild / canal / utilizator) [doar adaptor Discord]
|
||||
+-- limits.py max procese, timeout tur, rate limit, plafon de cost
|
||||
+-- security/ hook PreToolUse (confirmari) + wrapper `infra`
|
||||
+-- alerts.py alerte email [ops]
|
||||
+-- cleanup.py procese lasate in urma (`!cleanup`) [ops]
|
||||
+-- cleanup.py procese lasate in urma (`/cleanup`) [ops]
|
||||
|
|
||||
v
|
||||
claude -p --input-format stream-json --output-format stream-json --verbose
|
||||
@@ -44,9 +44,9 @@ Cateva alegeri care nu se vad din diagrama:
|
||||
(verificat: mesaj la 8s intr-un tur de 34.5s). Reaper la 20 min de inactivitate;
|
||||
repornirea se face cu `--resume <sid>`, deci firul nu-si pierde contextul.
|
||||
- **Un proces `claude` = ~406 MB RSS** (masurat). De aici toate limitele: maxim 4 procese
|
||||
vii, `MemoryMax=6G` pe unit, si comanda `!cleanup`.
|
||||
vii, `MemoryMax=6G` pe unit, si comanda `/cleanup`.
|
||||
- **Model implicit `sonnet`.** Un tur banal pe opus a costat $0.1547 (masurat), deci
|
||||
opus e optional, per fir, prin `!model opus`.
|
||||
opus e optional, per fir, prin `/model model:opus`.
|
||||
|
||||
---
|
||||
|
||||
@@ -54,24 +54,30 @@ Cateva alegeri care nu se vad din diagrama:
|
||||
|
||||
| Comanda | Ce face |
|
||||
|---------|---------|
|
||||
| `!new` | Sesiune noua, curata, in firul curent |
|
||||
| `!new --fork` | Sesiune noua care porneste din contextul celei curente |
|
||||
| `!cd <cale>` | Schimba directorul de lucru al firului (ex. `!cd /workspace/romfastsql`) |
|
||||
| `!model <sonnet\|opus>` | Schimba modelul pentru firul curent |
|
||||
| `!status` | Sesiune, director, model, cost cumulat, proces viu, ultimele linii de stderr |
|
||||
| `!stop` | Opreste turul in desfasurare din firul curent |
|
||||
| `!cleanup` | Listeaza procesele lasate in urma (rulare seaca). `!cleanup --force` le opreste |
|
||||
| `/new` | Sesiune noua, curata, in firul curent |
|
||||
| `/new fork:True` | Sesiune noua care porneste din contextul celei curente |
|
||||
| `/cd cale:<cale>` | Schimba directorul de lucru al firului (ex. `/cd cale:/workspace/romfastsql`) |
|
||||
| `/model model:<sonnet\|opus>` | Schimba modelul pentru firul curent |
|
||||
| `/status` | Sesiune, director, model, cost cumulat, proces viu, ultimele linii de stderr |
|
||||
| `/stop` | Opreste turul in desfasurare din firul curent |
|
||||
| `/cleanup` | Listeaza procesele lasate in urma (rulare seaca). `/cleanup force:True` le opreste |
|
||||
| `/help` | Lista de mai sus, in fir |
|
||||
|
||||
Comenzile sunt **application commands** (`/`), inregistrate pe guild-urile din
|
||||
`DISCORD_GUILD_IDS` la pornirea botului, deci apar in lista de comenzi a Discord. Vechiul
|
||||
prefix `!` nu mai executa nimic: botul raspunde doar cu indiciul catre comanda `/`
|
||||
echivalenta. Orice alt mesaj din canal pleaca la Claude ca prompt, ca inainte.
|
||||
|
||||
Un fir de Discord = o sesiune Claude. Canalul principal are si el sesiunea lui, cea
|
||||
implicita. Subsolul fiecarui raspuns arata modelul, durata si costul.
|
||||
|
||||
### Despre `!cleanup`
|
||||
### Despre `/cleanup`
|
||||
|
||||
`KillMode=control-group` opreste arborele serviciului la restart, dar **nu prinde ce s-a
|
||||
desprins**: un server pornit cu `&` intr-un tur, un `nohup`, un job lung reparentat la
|
||||
init. Alea raman si se aduna — 406 MB bucata, pe un container cu istoric de OOM.
|
||||
|
||||
`!cleanup` cauta doua feluri de resturi: procese `claude` care nu apar in `state.json`,
|
||||
`/cleanup` cauta doua feluri de resturi: procese `claude` care nu apar in `state.json`,
|
||||
si copii reparentati la init ramasi in cgroup-ul serviciului. **Ruleaza sec (dry-run) in
|
||||
mod implicit** — intai vezi lista, apoi decizi. Ce e inregistrat in `state.json` si toti
|
||||
descendentii acelor procese (adica turul care ruleaza chiar acum) nu sunt niciodata
|
||||
@@ -127,18 +133,21 @@ Nu se poate automatiza: cere un om logat in Discord.
|
||||
Nu ai nevoie de aprobare de la Discord: verificarea e ceruta abia de la 100 de servere.
|
||||
(*Server Members* si *Presence* nu sunt necesare — lasa-le oprite.)
|
||||
5. **Invita botul intr-un guild PRIVAT** al tau. *OAuth2* -> *URL Generator* ->
|
||||
scopes: `bot` -> permisiuni: *View Channel*, *Send Messages*, *Read Message History*,
|
||||
scopes: `bot` **si** `applications.commands` -> permisiuni: *View Channel*, *Send Messages*, *Read Message History*,
|
||||
*Create Public Threads*, *Send Messages in Threads*, *Attach Files*,
|
||||
*Embed Links*, *Add Reactions*. Deschide URL-ul generat si alege serverul.
|
||||
Bifele sunt greu de nimerit pe telefon; linkul echivalent, gata calculat
|
||||
(`APPLICATION_ID` e in *General Information*):
|
||||
|
||||
```
|
||||
https://discord.com/oauth2/authorize?client_id=APPLICATION_ID&scope=bot&permissions=309237763136
|
||||
https://discord.com/oauth2/authorize?client_id=APPLICATION_ID&scope=bot%20applications.commands&permissions=309237763136
|
||||
```
|
||||
|
||||
`309237763136` = exact permisiunile de mai sus. Fara *View Channel* botul nu vede
|
||||
canalul deloc, oricat de permis ar fi in allowlist.
|
||||
canalul deloc, oricat de permis ar fi in allowlist. Fara scope-ul
|
||||
`applications.commands` botul merge, dar inregistrarea comenzilor `/` esueaza cu
|
||||
*403 Missing Access* (scrie in log linkul de reinvitare) si comenzile nu apar in lista.
|
||||
Un bot deja invitat se re-invita cu acelasi link: se adauga doar scope-ul lipsa.
|
||||
**Nu-l invita intr-un server cu alti oameni** — cine scrie in canalul permis
|
||||
comanda direct containerul.
|
||||
6. **Ia ID-urile pentru allowlist.** In Discord: *Settings* -> *Advanced* ->
|
||||
@@ -178,7 +187,7 @@ systemctl --user restart claude-discord # repornire
|
||||
### Cost
|
||||
|
||||
Costul se vede in trei locuri: in subsolul fiecarui raspuns (turul curent + cumulat pe
|
||||
fir), in `!status`, si in `state.json` la cheia `cost`. La atingerea plafonului zilnic
|
||||
fir), in `/status`, si in `state.json` la cheia `cost`. La atingerea plafonului zilnic
|
||||
(`COST_CAP_USD_DAY`) botul nu mai accepta tururi noi si trimite email. Plafonul se
|
||||
reseteaza la schimbarea zilei.
|
||||
|
||||
@@ -227,9 +236,10 @@ tail -2 ~/.claude-discord/logs/alerts.log
|
||||
|---------|---------|
|
||||
| Botul nu raspunde deloc in Discord | `systemctl --user status claude-discord`. Daca e `failed`, `journalctl --user -u claude-discord -n 100`. Cauza #1: token invalid sau **MESSAGE CONTENT INTENT** oprit. |
|
||||
| Botul e viu dar ignora mesajele | Allowlist. Verifica `DISCORD_GUILD_IDS` / `DISCORD_CHANNEL_IDS` / `DISCORD_USER_IDS` din env. Respingerea e **tacuta**, intentionat. |
|
||||
| Comenzile `/` nu apar in lista din Discord | Botul a fost invitat fara scope-ul `applications.commands`. In log: `sync de comenzi slash esuat` + linkul de reinvitare. Reinvita botul, apoi `systemctl --user restart claude-discord`. |
|
||||
| Unitul se invarte in restart | Dupa 5 porniri esuate in 300s systemd renunta si lasa unitul `failed` (e voit). Repara, apoi `systemctl --user reset-failed claude-discord && systemctl --user start claude-discord`. |
|
||||
| Firul e blocat pe hourglass | Botul a fost restartat la mijlocul unui tur. Turul **nu** se reia automat (risc de dubla executie sub `bypassPermissions`); sweep-ul de la pornire pune un avertisment in fir. Trimite mesajul din nou. |
|
||||
| Memoria containerului creste | `!cleanup` (sec), apoi `!cleanup --force`. Vezi si `systemctl --user show claude-discord -p MemoryCurrent`. |
|
||||
| Memoria containerului creste | `/cleanup` (sec), apoi `/cleanup force:True`. Vezi si `systemctl --user show claude-discord -p MemoryCurrent`. |
|
||||
| „Plafon de cost atins" | E limita zilnica, nu o eroare. Ridica `COST_CAP_USD_DAY` in env si reporneste, sau asteapta ziua urmatoare. |
|
||||
| Nu vin emailuri de alerta | `command -v mail`; `mailq`; `tail ~/.claude-discord/logs/alerts.log`. Un `NESENT` in log iti spune exact de ce. |
|
||||
| Dupa reboot serviciul nu porneste | `loginctl show-user claude -p Linger` trebuie sa fie `yes`. Daca nu: `sudo loginctl enable-linger claude`. |
|
||||
@@ -289,7 +299,7 @@ baza pe ele ca pe o bariera.
|
||||
(1s -> 5s), dar cand Discord franeaza nu apare niciun mesaj: raspunsul doar apare mai
|
||||
incet. E singura cale fara test din analiza modurilor de esec — acceptata, fiindca
|
||||
esecul e intarziere, nu pierdere.
|
||||
- **`!cleanup` nu e infailibil.** Prinde procese `claude` neinregistrate si copii
|
||||
- **`/cleanup` nu e infailibil.** Prinde procese `claude` neinregistrate si copii
|
||||
reparentati la init ramasi in cgroup. Un proces care a iesit din cgroup *si* nu arata
|
||||
a `claude` (un `python -m http.server` desprins complet, de exemplu) ii scapa.
|
||||
Lista `NEVER_KILL` din `cleanup.py` protejeaza infrastructura sesiunii (systemd, sshd,
|
||||
@@ -312,12 +322,13 @@ baza pe ele ca pe o bariera.
|
||||
| `stream.py` | parser tolerant de stream JSONL | A |
|
||||
| `render.py` | chunker + loop de editare per canal | A |
|
||||
| `limits.py` | max procese, timeout, rate limit, plafon de cost | A |
|
||||
| `commands_slash.py` | declararea si inregistrarea comenzilor `/` | A |
|
||||
| `config.py` | citeste `~/.claude-discord/env` | A |
|
||||
| `security/confirm_hook.py` | hook `PreToolUse`, fail-closed | B |
|
||||
| `security/approvals.py` | canal de aprobari hook <-> bot | B |
|
||||
| `security/infra` | wrapper cu lista de hosturi permise | B |
|
||||
| `alerts.py` | alerte email, dedup 1h, nu arunca niciodata | C |
|
||||
| `cleanup.py` | `!cleanup`: orfani, dry-run implicit | C |
|
||||
| `cleanup.py` | `/cleanup`: orfani, dry-run implicit | C |
|
||||
| `ops/claude-discord.service` | unit systemd de utilizator | C |
|
||||
| `ops/install.sh` | instalare idempotenta | C |
|
||||
| `ops/env.example` | sablon de configurare | C |
|
||||
|
||||
Reference in New Issue
Block a user