Skip to content

Commit 4a45ccb

Browse files
committed
Add Pi.dev AI assistant: per-device chat + scheduled & bulk AI tasks
Adds an in-portal AI assistant scoped to a single device, scheduled AI tasks, and bulk AI commands across many devices. All device actions go through the existing TRMM REST API. Backend: - AIProvider / AIModel catalog and CoreSettings toggles - Role can_use_ai / can_use_ai_autoapprove / ai_allowed_models - PiPerms / AITaskPerms / BulkAIPerms; short-lived redis session tokens - /agents/<id>/pi/ session + history endpoints - /core/ai/ providers, models, available-models, tasks, runs, bulk endpoints - AITask / AITaskRun with Celery poller + runner; verdict -> custom alert - task + bulk scheduling: run Now (one-shot, self-disabling) or Scheduled (interval/daily/weekly/monthly); shared next-run computer - BulkAICommand: target All/Client/Site/Agents or dynamic Filter rules, skip offline agents; bulk target preview returns matched online devices - PI_RUN_TIMEOUT (default 3600s) with redis verdict recovery Bridge (pibridge/): - Node service embedding the assistant runtime, deployed to /opt/pi-trmm-bridge; device-scoped tools call the TRMM REST API only - Linux command timeout guard, per-session logging, WebSocket heartbeat Install/update: - install.sh adds the nginx /pi/ location and calls pibridge/setup.sh - update.sh calls pibridge/setup.sh (idempotent) See PI_AI_ASSISTANT.md for full documentation.
1 parent 54eab4c commit 4a45ccb

31 files changed

Lines changed: 3371 additions & 0 deletions

PI_AI_ASSISTANT.md

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
# Pi.dev AI Assistant for Tactical RMM
2+
3+
An in-portal AI assistant that operates on a **single device at a time** through
4+
Tactical RMM's existing agent channel, plus **scheduled AI tasks** that
5+
periodically check a device and raise TRMM alerts when they find a problem.
6+
7+
- Right-click a device → **Pi.dev** → chat window scoped to that device.
8+
- The assistant acts on the device with **the same REST endpoints** TRMM already
9+
uses (`/cmd/`, `/runscript/`, processes, event logs, software, checks…), so
10+
everything it does is audited and goes over the normal agent path.
11+
- Global settings hold provider API keys + a model catalog; role permissions
12+
gate who can use it and which models they can use.
13+
- Scheduled tasks run headlessly, report a verdict, and create alerts.
14+
15+
---
16+
17+
## 1. Architecture
18+
19+
```
20+
Browser (portal)
21+
AgentActionMenu → "Pi.dev" → popup /pichat/:agent_id
22+
POST /agents/<id>/pi/session/ (Knox auth + PiPerms → short-lived redis token)
23+
WebSocket wss://<api>/pi/ws/<token>/
24+
25+
nginx location ~ ^/pi/ → 127.0.0.1:8787
26+
27+
pi-trmm-bridge (Node, systemd, /opt/pi-trmm-bridge)
28+
• reads redis pi_session:<token> (written by Django; never exposed to browser)
29+
• one AI session per chat, scoped to the agent
30+
• built-in shell/edit/write DISABLED; custom tools call the TRMM REST API
31+
• streams events → WebSocket → chat window
32+
│TRMM REST (service API key) │LLM provider (key from settings)
33+
Django (/rmm) → NATS → device Anthropic / OpenAI / …
34+
```
35+
36+
Scheduled tasks use the same bridge via a headless `POST /pi/run` endpoint driven
37+
by a Celery beat poller.
38+
39+
### Components added
40+
41+
| Layer | What |
42+
|-------|------|
43+
| Backend (`api/tacticalrmm`) | `AIProvider`, `AIModel`, `AITask`, `AITaskRun` models; `CoreSettings` AI toggles; `Role.can_use_ai` / `can_use_ai_autoapprove` / `ai_allowed_models`; `PiPerms`; endpoints under `/core/ai/…` and `/agents/<id>/pi/…`; Celery tasks `dispatch_due_ai_tasks` + `run_ai_task`; redis token helper `agents/pi_session.py` |
44+
| Bridge (`pibridge/`) | Node service embedding the AI runtime; deployed to `/opt/pi-trmm-bridge` by `pibridge/setup.sh` |
45+
| Frontend (`tacticalrmm-web`) | "Pi.dev" menu item + `PiChat.vue`; AI settings tab; role permissions; `AIHistoryTab`, `AITasksTab` (per-device + company aggregate) |
46+
| Install/Update | `install.sh` and `update.sh` call `pibridge/setup.sh`; nginx `/pi/` block added |
47+
48+
---
49+
50+
## 2. Installation & updates (automatic)
51+
52+
Fresh installs and upgrades set everything up with **no manual steps**:
53+
54+
- `install.sh` builds the nginx `/pi/` location block into `rmm.conf` and calls
55+
`pibridge/setup.sh`.
56+
- `update.sh` calls `pibridge/setup.sh` (idempotent) after migrations/restarts.
57+
58+
`pibridge/setup.sh` (idempotent) does:
59+
60+
1. Deploys `pibridge/``/opt/pi-trmm-bridge` and runs `npm install` (incl. the
61+
AI runtime SDK).
62+
2. Ensures a TRMM **service API key** (`pi-bridge`, role `pi-bridge-service`) the
63+
bridge uses to act on devices.
64+
3. Writes `/etc/pi-trmm-bridge.env` (port, redis, API url, key, session dir).
65+
4. Writes/enables `pi-trmm-bridge.service` (systemd).
66+
5. Injects the nginx `/pi/` block on existing installs if missing.
67+
6. Restarts the bridge.
68+
69+
Manual run if ever needed:
70+
71+
```bash
72+
bash /rmm/pibridge/setup.sh
73+
sudo systemctl status pi-trmm-bridge
74+
curl -s http://127.0.0.1:8787/pi/health # {"ok":true}
75+
tail -f /var/log/pi-trmm-bridge.log
76+
```
77+
78+
---
79+
80+
## 3. Configure providers & models
81+
82+
**Global Settings → Pi.dev AI** (requires `can_edit_core_settings`):
83+
84+
- **Enable Pi.dev module** (master switch), **Require approval for device
85+
actions**, **Persist chat history**.
86+
- **Providers** — add a provider (Anthropic / OpenAI / Google / xAI / OpenRouter
87+
/ custom) and paste its API key (stored server-side, write-only).
88+
- **Models** — pick from a dropdown that lists **exactly the models available for
89+
your configured keys**, set a display name, thinking level, enable, and mark
90+
one **default**.
91+
92+
Example: add Anthropic with your key, add model `claude-sonnet-4-5` as default.
93+
94+
Under the hood:
95+
96+
```
97+
POST /core/ai/providers/ {"name":"anthropic","api_key":"sk-...","enabled":true}
98+
GET /core/ai/available-models/ → models the current keys can use
99+
POST /core/ai/models/ {"provider":1,"model_id":"claude-sonnet-4-5",
100+
"display_name":"Claude Sonnet 4.5","is_default":true,"enabled":true}
101+
```
102+
103+
---
104+
105+
## 4. Who can use it (roles)
106+
107+
**Accounts → Roles → (role) → Pi.dev AI**:
108+
109+
- **Use Pi.dev AI Assistant** (`can_use_ai`) — required to open chats and to see /
110+
manage scheduled tasks.
111+
- **Allow auto-approve of device actions** (`can_use_ai_autoapprove`) — lets that
112+
role toggle auto-approve in a chat.
113+
- **Allowed AI models** — multiselect; empty = the global default only.
114+
115+
Model access is enforced **server-side**: the session token only carries the
116+
models the role may use, and the bridge refuses anything else. Superusers get all
117+
enabled models.
118+
119+
**Scope:** chats and scheduled tasks are further limited by the role's normal
120+
agent access — a tech only sees/manages tasks for devices in the clients/sites
121+
they're allowed to view (task lists are filtered by `Agent.filter_by_role`, and
122+
per-device actions are checked with `_has_perm_on_agent`). Configuring providers /
123+
models is separate and still requires core-settings admin
124+
(`can_view/edit_core_settings`).
125+
126+
---
127+
128+
## 5. Interactive chat
129+
130+
Right-click a device → **Pi.dev** opens a popup scoped to that device.
131+
132+
- The system prompt is seeded with the device facts (hostname, client/site, OS,
133+
logged-in user, IPs…). The assistant has an effective **root/console shell** on
134+
the device via `run_command_on_device` (each call is a fresh non-interactive
135+
shell; it batches steps with `;`/`&&`).
136+
- **Mutating actions** (run command/script, kill process, reboot) require an
137+
in-window **Approve** click by default. Roles with auto-approve can flip a
138+
per-session toggle.
139+
- **Switch models mid-conversation** from the dropdown — the model changes on the
140+
**same session**, history preserved (no reset).
141+
- Everything the assistant runs shows as a tool card with the exact command and
142+
its output.
143+
144+
Example prompts:
145+
146+
- *"What's using all the memory right now?"*
147+
- *"List the docker containers and tell me if any are unhealthy."*
148+
- *"The disk is filling up — find the biggest directories under /var."*
149+
150+
### 6.1 AI History
151+
152+
Device view → **AI History** tab shows a unified log of AI activity on that
153+
device, with a **Source** column:
154+
155+
- **Chat** — interactive sessions; **Continue** resumes one (survives a dropped
156+
connection), **New chat** starts fresh.
157+
- **Task: <name>** — a scheduled task run; **View** shows its transcript.
158+
- **Bulk: <name>** — a run from a Bulk AI Command (§7); **View** shows its
159+
transcript.
160+
161+
Chat sessions persist per `agent_id`; task/bulk runs come from `AITaskRun`.
162+
163+
---
164+
165+
## 6. Scheduled AI Tasks
166+
167+
Device view → **AI Tasks** tab (or the company aggregate, §7).
168+
169+
Create a task with:
170+
171+
- **Prompt** — what to check, e.g. *"Check SQL Server performance. Alert if any
172+
data file write latency is over 1s or wait stats look bad; warn over 500ms;
173+
otherwise ok."*
174+
- **Model** (or global default), **Alert threshold** (never / warning+alert /
175+
alert-only).
176+
- **When to run**: **Now** (one-shot — runs when you click Run now, then
177+
auto-disables itself, kept with its results) or **Scheduled**.
178+
- **Scheduled** options: **Every N minutes**, **Daily**, **Weekly** (pick days),
179+
or **Monthly** (day of month). (Legacy one-time tasks still run at their target
180+
and disable afterward.)
181+
- **Allow changes** — off by default. Off = read-only diagnostics (can run shell
182+
commands described in the prompt, but destructive tools are blocked). On =
183+
allows run-script / kill / reboot for unattended remediation.
184+
185+
### How a task decides "something is wrong"
186+
187+
The scheduled run has a `report_result` tool it must call once with a verdict:
188+
189+
```
190+
report_result(status: "ok" | "warning" | "alert", summary, details)
191+
```
192+
193+
Django maps the verdict to a TRMM alert severity and, if it meets the threshold,
194+
creates a **custom Alert** on the device (dashboard + alert-template routing):
195+
196+
| Verdict | Severity | Alerts when threshold is… |
197+
|---------|----------|---------------------------|
198+
| `ok` || never |
199+
| `warning` | WARNING | "warning" |
200+
| `alert` | ERROR | "warning" or "alert" |
201+
| run error | WARNING | "warning" or "alert" |
202+
203+
### Scheduling
204+
205+
A Celery beat poller (`dispatch_due_ai_tasks`, every minute) queues due tasks;
206+
`run_ai_task` calls the bridge's headless `POST /pi/run`, records an `AITaskRun`,
207+
updates the task, and raises the alert if needed. One-time tasks store a computed
208+
`run_at` target and set `enabled=False` after their single run.
209+
210+
### Run history & live tracing
211+
212+
- **Run now** starts the task immediately and opens a **live trace** window that
213+
streams each tool call, its output, and the final verdict in real time.
214+
- The **history** view is master-detail: every run on the left (status + brief
215+
summary + when); click one to see the full transcript of everything it did.
216+
217+
---
218+
219+
## 7. Bulk AI Commands
220+
221+
**Tools → Bulk AI Command** runs one AI prompt across **many devices**, on demand
222+
or on a schedule. Offline agents are skipped at run time.
223+
224+
- **Targeting** mirrors Bulk Command: **All / Client / Site / Agents / Filter**.
225+
- The Agents picker lists machines with their **client / site** shown faded,
226+
and filters as you type (by hostname, client, or site).
227+
- **Filter** mode builds dynamic rules (a `+` adds conditions, all ANDed):
228+
e.g. *client contains "Lehigh" AND hostname contains "VM"*. Fields: hostname,
229+
client, site, description, OS, platform, monitoring type. Operators: contains,
230+
does-not-contain, equals, does-not-equal, starts-with. A live preview shows
231+
how many online devices match.
232+
- Type/OS quick filters apply to All/Client/Site targets.
233+
- **When to run**: **Now** (one-shot — runs on the online targets then disables
234+
itself, kept with its results) or **Scheduled** (Every N hours / Daily / Weekly
235+
/ Monthly).
236+
- Same options as tasks: model, alert threshold, read-only vs allow-changes.
237+
238+
Each per-device execution is recorded as an `AITaskRun` tagged to the bulk
239+
command, so it appears in that device's **AI History** (§6.1) with a
240+
**"Bulk: <name>"** source, and raises alerts per the threshold.
241+
242+
Endpoints (`BulkAIPerms` = `can_use_ai` + `can_run_bulk`):
243+
244+
```
245+
GET/POST /core/ai/bulk/ , PUT/DELETE /core/ai/bulk/<id>/
246+
POST /core/ai/bulk/<id>/run/ (run now)
247+
POST /core/ai/bulk/preview/ (count online agents a target would hit)
248+
```
249+
250+
Scheduler: `dispatch_due_bulk_ai_commands` (beat, every minute) fans a due
251+
command out to `run_bulk_ai_agent` per online target.
252+
253+
## 8. Company-wide view (Client / Site)
254+
255+
Select a **Client** or **Site** in the tree, then open the **AI Tasks** tab in
256+
the bottom panel: instead of "No agent selected" it shows **every task across all
257+
devices in that company** with a **Hostname** column and an **aggregate status
258+
summary** (counts of OK / Warning / Alert / Error / Never-run), worst-status
259+
sorted, filterable. Run-now and history work per row.
260+
261+
---
262+
263+
## 9. API reference
264+
265+
Device-scoped (Knox auth, `PiPerms`):
266+
267+
```
268+
POST /agents/<agent_id>/pi/session/ → { token, url, model_id, allowed_models, ... }
269+
GET /agents/<agent_id>/pi/history/ → { sessions: [...] }
270+
DELETE /agents/<agent_id>/pi/history/ { "session_id": "..." }
271+
WS /pi/ws/<token>/ (via the bridge)
272+
```
273+
274+
Provider / model config (Knox auth, core-settings admin
275+
`can_view/edit_core_settings`):
276+
277+
```
278+
GET/POST /core/ai/providers/ , PUT/DELETE /core/ai/providers/<id>/
279+
GET /core/ai/available-models/
280+
GET/POST /core/ai/models/ , PUT/DELETE /core/ai/models/<id>/
281+
```
282+
283+
Scheduled tasks & runs (Knox auth, `AITaskPerms` = `can_use_ai` + per-agent
284+
access; lists are scoped to the role's visible clients/sites):
285+
286+
```
287+
GET/POST /core/ai/tasks/ , PUT/DELETE /core/ai/tasks/<id>/
288+
(filter: ?agent_id= | ?site=<id> | ?client=<id>)
289+
POST /core/ai/tasks/<id>/run/ (run now)
290+
GET /core/ai/runs/?task_id=<id> (run history)
291+
GET /core/ai/runs/<run_id>/live/ (live progress from redis)
292+
```
293+
294+
Bridge (localhost only):
295+
296+
```
297+
GET /pi/health
298+
POST /pi/run (headless scheduled run → {status, summary, transcript})
299+
POST /pi/models (available models for provider keys)
300+
GET /pi/history/<agent_id> (session index)
301+
WS /pi/ws/<token>/ (interactive chat)
302+
```
303+
304+
---
305+
306+
## 10. Reliability & troubleshooting
307+
308+
Several safeguards keep a chat or task from appearing "stuck":
309+
310+
- **Command timeout guard** — on Linux, `run_command_on_device` wraps the command
311+
in `timeout`, so a hung command (e.g. a stuck Proxmox `qm list`/pmxcfs) is
312+
terminated and returns partial output plus a clear note instead of blocking the
313+
session.
314+
- **Chat stall watchdog** — the chat window shows an elapsed timer while working,
315+
and after ~45s with no activity it warns that the model/device may be slow and
316+
offers a **Stop** button.
317+
- **Connection recovery** — if the WebSocket drops mid-response, the window shows
318+
a **Reconnect** button that resumes the same conversation (via session id).
319+
- **Auto-retry visibility** — transient provider errors are retried and surfaced
320+
in the chat (“provider was busy; retrying…”).
321+
- **WebSocket heartbeat** — the bridge pings clients and drops zombie connections.
322+
- **Per-session logging** — the bridge logs each tool call, retries, and errors to
323+
`/var/log/pi-trmm-bridge.log` for diagnosis.
324+
- **Long scheduled runs** — the runner waits up to `PI_RUN_TIMEOUT` (default
325+
3600s) for a task to finish, so lengthy investigations/remediations aren't cut
326+
off. If the HTTP call still times out, it recovers the verdict from the
327+
bridge's redis progress when available. Raise `PI_RUN_TIMEOUT` in
328+
`local_settings.py` for tasks that need even longer.
329+
330+
## 11. Security model
331+
332+
1. Gated by **module enabled** (global) + **`can_use_ai`** (role) + agent scope,
333+
enforced when the session token is minted.
334+
2. Model choice enforced server-side against the role's allowed list.
335+
3. The bridge has **no shell on the RMM server** (built-in shell/edit/write
336+
disabled); it only acts on the target device through TRMM's REST API, hard-
337+
bound to the one `agent_id` in the token.
338+
4. Mutating device actions require in-window approval by default; unattended
339+
tasks are read-only unless explicitly allowed.
340+
5. Provider keys and the TRMM service key stay server-side; the browser only ever
341+
receives a short-lived (8h) redis token over TLS.
342+
6. Device data and command output are sent to the configured LLM provider — this
343+
is inherent to the feature and should be acceptable under your data policy
344+
before enabling the module.
345+
346+
---
347+
348+
## 12. Bridge environment (`/etc/pi-trmm-bridge.env`)
349+
350+
```
351+
PORT=8787
352+
HOST=127.0.0.1
353+
REDIS_URL=redis://127.0.0.1:6379
354+
TRMM_API_URL=https://<your-api-domain>
355+
TRMM_API_KEY=<service key, auto-created by setup.sh>
356+
PI_SESSIONS_ROOT=/opt/pi-trmm-bridge/sessions
357+
IDLE_TIMEOUT_MS=1800000
358+
MAX_SESSIONS=10
359+
```
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Generated by Django 4.2.30 on 2026-07-07 15:06
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('core', '0054_aiprovider_coresettings_ai_module_enabled_and_more'),
10+
('accounts', '0042_copy_mesh_permission_to_terminal'),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name='role',
16+
name='ai_allowed_models',
17+
field=models.ManyToManyField(blank=True, related_name='role_ai_models', to='core.aimodel'),
18+
),
19+
migrations.AddField(
20+
model_name='role',
21+
name='can_use_ai',
22+
field=models.BooleanField(default=False),
23+
),
24+
migrations.AddField(
25+
model_name='role',
26+
name='can_use_ai_autoapprove',
27+
field=models.BooleanField(default=False),
28+
),
29+
]

0 commit comments

Comments
 (0)