Updated to version 1.2.6

This commit is contained in:
2026-04-28 09:41:56 +02:00
parent c5a5356a0d
commit c951185cdc
12 changed files with 707 additions and 145 deletions
+154 -108
View File
@@ -23,7 +23,12 @@
<li><a href="#chat-badges">Capability Badges</a></li>
</ul>
</li>
<li><a href="#files">Files</a></li>
<li>
<a href="#files">Files</a>
<ul>
<li><a href="#files-upload">Uploading Files</a></li>
</ul>
</li>
<li>
<a href="#agents">Agents</a>
<ul>
@@ -92,7 +97,7 @@
oAI-Web (agent name: <strong>{{ agent_name }}</strong>) is a secure, self-hosted personal AI agent
built on the Claude API with full tool-use support. It runs on your home server and exposes a clean
web interface for use inside your local network. The agent can read email, browse the web, manage
calendar events, read and write files, send push notifications, generate images, and more all via
calendar events, read and write files, send push notifications, generate images, and more - all via
a structured tool-use loop with optional confirmation prompts before side-effects.
</p>
@@ -100,7 +105,7 @@
<ul>
<li>An API key for at least one AI provider: <strong>Anthropic</strong> or <strong>OpenRouter</strong></li>
<li>Python 3.12+ (or Docker)</li>
<li><strong>PostgreSQL</strong> (with asyncpg) the main application database</li>
<li><strong>PostgreSQL</strong> (with asyncpg) - the main application database</li>
<li>PostgreSQL + <strong>pgvector</strong> extension only required if you use the <em>2nd Brain</em> feature (can be the same server)</li>
</ul>
@@ -111,7 +116,7 @@
<li>On first boot with zero users, you are redirected to <code>/setup</code> to create the first admin account</li>
<li>Open <a href="/settings">Settings</a><strong>Credentials</strong> and add any additional credentials (CalDAV, email, Pushover, etc.)</li>
<li>Add email recipients via <strong>Settings → Whitelists → Email Whitelist</strong></li>
<li>Add filesystem directories via <strong>Settings → Whitelists → Filesystem Sandbox</strong> the agent cannot touch any path outside these directories</li>
<li>Add filesystem directories via <strong>Settings → Whitelists → Filesystem Sandbox</strong> - the agent cannot touch any path outside these directories</li>
<li>Optionally set <code>system:users_base_folder</code> in Credentials to enable per-user file storage (e.g. <code>/data/users</code>)</li>
<li>Optionally configure email accounts and Telegram via their respective Settings tabs</li>
</ol>
@@ -119,7 +124,7 @@
<h2>Key Concepts</h2>
<dl>
<dt>Agent</dt>
<dd>A configured AI persona with a model, system prompt, optional schedule, and restricted tool set. Agents run headlessly no confirmation prompts, results logged in run history.</dd>
<dd>A configured AI persona with a model, system prompt, optional schedule, and restricted tool set. Agents run headlessly - no confirmation prompts, results logged in run history.</dd>
<dt>Tool</dt>
<dd>A capability the AI can invoke: read a file, send an email, fetch a web page, generate an image, etc. Every tool call is logged in the Audit Log.</dd>
<dt>Confirmation</dt>
@@ -127,7 +132,7 @@
<dt>Audit Log</dt>
<dd>An append-only record of every tool call, its arguments, and outcome. Never auto-deleted unless you configure a retention period.</dd>
<dt>Credential Store</dt>
<dd>An AES-256-GCM encrypted key-value store in PostgreSQL. All secrets (API keys, passwords) live here never in the agent's context window.</dd>
<dd>An AES-256-GCM encrypted key-value store in PostgreSQL. All secrets (API keys, passwords) live here - never in the agent's context window.</dd>
<dt>User Folder</dt>
<dd>When <code>system:users_base_folder</code> is set, each user gets a personal folder at <code>{base}/{username}/</code>. Agents and the Files page scope all file access to this folder automatically.</dd>
</dl>
@@ -143,7 +148,7 @@
<h2>Sending Messages</h2>
<p>
Press <kbd>Enter</kbd> to send. Use <kbd>Shift+Enter</kbd> for a newline within your message.
The <strong>Clear History</strong> button (✕) in the status bar wipes the in-memory conversation for the current session the agent starts fresh.
The <strong>Clear History</strong> button (✕) in the status bar wipes the in-memory conversation for the current session - the agent starts fresh.
</p>
<h2 id="chat-attachments">File Attachments</h2>
@@ -151,7 +156,7 @@
The <strong>paperclip button</strong> (📎) in the input bar opens a file picker. Only shown when the active model supports vision or documents. Supported formats:
</p>
<ul>
<li><strong>Images</strong>: JPEG, PNG, GIF, WebP, AVIF shown as thumbnails in the preview strip</li>
<li><strong>Images</strong>: JPEG, PNG, GIF, WebP, AVIF - shown as thumbnails in the preview strip</li>
<li><strong>PDF</strong>: shown as a file chip with the filename in the preview strip</li>
</ul>
<p>
@@ -169,18 +174,18 @@
<h2 id="chat-badges">Capability Badges</h2>
<p>Small badges in the status bar show what the active model supports:</p>
<ul>
<li>🎨 <strong>Image Gen</strong> can generate images (use via the <code>image_gen</code> tool in agents)</li>
<li>👁 <strong>Vision</strong> can read images and PDFs; the attachment button is shown</li>
<li>🔧 <strong>Tools</strong> supports tool/function calling</li>
<li>🌐 <strong>Online</strong> has live web access built in</li>
<li>🎨 <strong>Image Gen</strong> - can generate images (use via the <code>image_gen</code> tool in agents)</li>
<li>👁 <strong>Vision</strong> - can read images and PDFs; the attachment button is shown</li>
<li>🔧 <strong>Tools</strong> - supports tool/function calling</li>
<li>🌐 <strong>Online</strong> - has live web access built in</li>
</ul>
<h2>Tool Indicators</h2>
<p>While the agent is working, small badges appear below each message:</p>
<ul>
<li><span style="color:var(--accent)"></span> <strong>Pulsing blue</strong> tool is currently running</li>
<li><span style="color:var(--green)"></span> <strong>Solid green</strong> tool completed successfully</li>
<li><span style="color:var(--red)"></span> <strong>Solid red</strong> tool failed or returned an error</li>
<li><span style="color:var(--accent)"></span> <strong>Pulsing blue</strong> - tool is currently running</li>
<li><span style="color:var(--green)"></span> <strong>Solid green</strong> - tool completed successfully</li>
<li><span style="color:var(--red)"></span> <strong>Solid red</strong> - tool failed or returned an error</li>
</ul>
<h2>Confirmation Modal</h2>
@@ -210,16 +215,38 @@
<h2>Downloading</h2>
<ul>
<li><strong>Download</strong> downloads an individual file.</li>
<li><strong>↓ ZIP</strong> downloads an entire folder (and its contents) as a ZIP archive. The <strong>Download folder as ZIP</strong> button in the header always downloads the current folder.</li>
<li><strong>Download</strong> - downloads an individual file.</li>
<li><strong>↓ ZIP</strong> - downloads an entire folder (and its contents) as a ZIP archive. The <strong>Download folder as ZIP</strong> button in the header always downloads the current folder.</li>
</ul>
<h2 id="files-upload">Uploading Files</h2>
<p>
The <strong>Upload</strong> button (↑ arrow icon) in the header lets you upload one or more files directly into your current folder.
</p>
<ul>
<li>Click <strong>Upload</strong> and select one or more files from your device.</li>
<li>Files are uploaded to whichever folder you are currently browsing - navigate to the target folder first.</li>
<li>If a file with the same name already exists it will be overwritten without a prompt, so check the folder contents before uploading.</li>
<li>The file list refreshes automatically once the upload completes.</li>
</ul>
<h3>Upload limits</h3>
<p>Uploads are restricted by a policy configurable by your administrator under <strong>Settings → Security → File Upload Policy</strong>:</p>
<ul>
<li><strong>Allowed types</strong>: only common text/code files, images (JPG, PNG, GIF, WebP, SVG, …), and PDF are accepted by default. Files with other extensions are rejected.</li>
<li><strong>Max file size</strong>: 50 MB per file (default).</li>
<li><strong>Max files per upload</strong>: 20 files at once (default).</li>
</ul>
<p>Both limits are checked in the browser before the upload starts, and enforced again on the server. Rejected files are listed in a flash notification; accepted files in the same batch are still uploaded.</p>
<p class="help-note">
The Upload button is only shown when a data folder is configured for your account. If it is missing, ask your administrator to set <code>system:users_base_folder</code>.
</p>
<h2>Deleting Files</h2>
<p>
A red <strong>Delete</strong> button appears next to downloadable files. Clicking it shows a confirmation dialog before the file is permanently removed. Deletion is instant and cannot be undone.
</p>
<p class="help-note">
<strong>Protected files</strong>: files whose names start with <code>memory_</code> or <code>reasoning_</code> cannot be deleted from the UI. These are agent memory and decision logs maintained by email handling agents deleting them would disrupt the agent's continuity.
<strong>Protected files</strong>: files whose names start with <code>memory_</code> or <code>reasoning_</code> cannot be deleted from the UI. These are agent memory and decision logs maintained by email handling agents - deleting them would disrupt the agent's continuity.
</p>
<h2>No Folder Configured?</h2>
@@ -232,7 +259,7 @@
<section id="agents" data-section>
<h1>Agents</h1>
<p>
Agents are headless AI personas with a fixed system prompt, model, and optional cron schedule. Unlike interactive chat, agents run without confirmation modals their allowed tools are declared at creation time. Results and token usage are logged per-run in the <a href="/agents">Agents</a> page.
Agents are headless AI personas with a fixed system prompt, model, and optional cron schedule. Unlike interactive chat, agents run without confirmation modals - their allowed tools are declared at creation time. Results and token usage are logged per-run in the <a href="/agents">Agents</a> page.
</p>
<p class="help-note">
Email handling agents (created automatically by Email Accounts setup) are hidden from the Agents list and Status tab. They are managed exclusively via <strong>Settings → Email Accounts</strong>.
@@ -241,18 +268,18 @@
<h2 id="agents-creating">Creating an Agent</h2>
<p>Click <strong>New Agent</strong> on the Agents page. Required fields:</p>
<ul>
<li><strong>Name</strong> displayed in the UI and logs</li>
<li><strong>Model</strong> any model from a configured provider</li>
<li><strong>Prompt</strong> the agent's task description or system prompt (see Prompt Modes below)</li>
<li><strong>Name</strong> - displayed in the UI and logs</li>
<li><strong>Model</strong> - any model from a configured provider</li>
<li><strong>Prompt</strong> - the agent's task description or system prompt (see Prompt Modes below)</li>
</ul>
<p>Optional fields:</p>
<ul>
<li><strong>Description</strong> shown in the agent list for reference</li>
<li><strong>Schedule</strong> cron expression for automatic runs</li>
<li><strong>Allowed Tools</strong> restrict which tools the agent may use</li>
<li><strong>Max Tool Calls</strong> per-run limit (overrides the system default)</li>
<li><strong>Sub-agents</strong> toggle to allow this agent to create child agents</li>
<li><strong>Prompt Mode</strong> controls how the prompt is composed (see below)</li>
<li><strong>Description</strong> - shown in the agent list for reference</li>
<li><strong>Schedule</strong> - cron expression for automatic runs</li>
<li><strong>Allowed Tools</strong> - restrict which tools the agent may use</li>
<li><strong>Max Tool Calls</strong> - per-run limit (overrides the system default)</li>
<li><strong>Sub-agents</strong> - toggle to allow this agent to create child agents</li>
<li><strong>Prompt Mode</strong> - controls how the prompt is composed (see below)</li>
</ul>
<h2 id="agents-schedule">Scheduling</h2>
@@ -260,10 +287,10 @@
<pre>minute hour day-of-month month day-of-week</pre>
<p>Examples:</p>
<ul>
<li><code>0 8 * * 1-5</code> weekdays at 08:00</li>
<li><code>*/15 * * * *</code> every 15 minutes</li>
<li><code>0 9 * * 1</code> every Monday at 09:00</li>
<li><code>30 18 * * *</code> every day at 18:30</li>
<li><code>0 8 * * 1-5</code> - weekdays at 08:00</li>
<li><code>*/15 * * * *</code> - every 15 minutes</li>
<li><code>0 9 * * 1</code> - every Monday at 09:00</li>
<li><code>30 18 * * *</code> - every day at 18:30</li>
</ul>
<p>
Use the <strong>Enable / Disable</strong> toggle to pause a schedule without deleting the agent.
@@ -278,12 +305,12 @@
<dt>System only</dt>
<dd>The standard system prompt is used as-is; the agent prompt becomes the task message sent to the agent. Useful when you want {{ agent_name }}'s full personality but just need to specify a recurring task.</dd>
<dt>Agent only</dt>
<dd>The agent prompt <em>fully replaces</em> the system prompt no SOUL.md, no security rules, no USER.md context. Use with caution. Suitable for specialized agents with a completely different persona.</dd>
<dd>The agent prompt <em>fully replaces</em> the system prompt - no SOUL.md, no security rules, no USER.md context. Use with caution. Suitable for specialized agents with a completely different persona.</dd>
</dl>
<h2 id="agents-tools">Tool Restrictions</h2>
<p>
Leave <strong>Allowed Tools</strong> blank to give the agent access to all tools. Select specific tools to restrict only those tool schemas are sent to the model, making it structurally impossible to use undeclared tools.
Leave <strong>Allowed Tools</strong> blank to give the agent access to all tools. Select specific tools to restrict - only those tool schemas are sent to the model, making it structurally impossible to use undeclared tools.
</p>
<p>
MCP server tools appear as a single server-level toggle (e.g. <code>Gitea MCP</code>), which enables all tools from that server. Individual built-in tools are listed separately.
@@ -297,7 +324,7 @@
<h2>Image Generation in Agents</h2>
<p>
Agents can generate images using the <code>image_gen</code> tool. Important: the <strong>agent model must be a text/tool-use model</strong> (e.g. Claude Sonnet), not an image-generation model. The <code>image_gen</code> tool calls the image-gen model internally, saves the result to disk, and returns the file path. The default image-gen model is <code>openrouter:openai/gpt-5-image</code> override via the <code>system:default_image_gen_model</code> credential.
Agents can generate images using the <code>image_gen</code> tool. Important: the <strong>agent model must be a text/tool-use model</strong> (e.g. Claude Sonnet), not an image-generation model. The <code>image_gen</code> tool calls the image-gen model internally, saves the result to disk, and returns the file path. The default image-gen model is <code>openrouter:openai/gpt-5-image</code> - override via the <code>system:default_image_gen_model</code> credential.
</p>
<p>
Generated images are saved to the agent's user folder. The file path is returned as the tool result so the agent can reference it.
@@ -308,7 +335,7 @@
<section id="monitors" data-section>
<h1>Monitors</h1>
<p>
The <a href="/monitors">Monitors</a> page lets you watch web pages and RSS feeds for changes, then automatically dispatch an agent or send a Pushover notification when something new appears. Monitors run on a schedule in the background no manual checking needed.
The <a href="/monitors">Monitors</a> page lets you watch web pages and RSS feeds for changes, then automatically dispatch an agent or send a Pushover notification when something new appears. Monitors run on a schedule in the background - no manual checking needed.
</p>
<h2 id="monitors-pages">Page Watchers</h2>
@@ -317,18 +344,18 @@
</p>
<p>Fields when creating a page watcher:</p>
<ul>
<li><strong>Name</strong> displayed in the monitor list</li>
<li><strong>URL</strong> the page to watch</li>
<li><strong>Schedule</strong> cron expression (e.g. <code>0 * * * *</code> = every hour)</li>
<li><strong>CSS Selector</strong> optional; restricts the hash to a specific element on the page (e.g. <code>#price</code> or <code>.headline</code>). Leave blank to watch the entire page.</li>
<li><strong>Agent</strong> agent to dispatch when a change is detected</li>
<li><strong>Notification mode</strong> <code>agent</code> (dispatch the agent), <code>pushover</code> (send a push notification), or <code>both</code></li>
<li><strong>Name</strong> - displayed in the monitor list</li>
<li><strong>URL</strong> - the page to watch</li>
<li><strong>Schedule</strong> - cron expression (e.g. <code>0 * * * *</code> = every hour)</li>
<li><strong>CSS Selector</strong> - optional; restricts the hash to a specific element on the page (e.g. <code>#price</code> or <code>.headline</code>). Leave blank to watch the entire page.</li>
<li><strong>Agent</strong> - agent to dispatch when a change is detected</li>
<li><strong>Notification mode</strong> - <code>agent</code> (dispatch the agent), <code>pushover</code> (send a push notification), or <code>both</code></li>
</ul>
<p>
The table shows <strong>Last checked</strong> and <strong>Last changed</strong> timestamps. Use the <strong>Check now</strong> button to force an immediate check outside the schedule.
</p>
<p class="help-note">
Page watchers use plain HTTP (not a real browser). For JavaScript-heavy pages where the interesting content is rendered client-side, the CSS selector approach may not work the agent's browser tool is better suited for those.
Page watchers use plain HTTP (not a real browser). For JavaScript-heavy pages where the interesting content is rendered client-side, the CSS selector approach may not work - the agent's browser tool is better suited for those.
</p>
<h2 id="monitors-rss">RSS Feeds</h2>
@@ -337,12 +364,12 @@
</p>
<p>Fields when creating an RSS monitor:</p>
<ul>
<li><strong>Name</strong> displayed in the monitor list</li>
<li><strong>Feed URL</strong> any RSS or Atom feed URL</li>
<li><strong>Schedule</strong> cron expression (e.g. <code>0 */4 * * *</code> = every 4 hours)</li>
<li><strong>Agent</strong> agent to dispatch for new items</li>
<li><strong>Max items per run</strong> cap on how many new items trigger the agent in one run (default: 5)</li>
<li><strong>Notification mode</strong> <code>agent</code>, <code>pushover</code>, or <code>both</code></li>
<li><strong>Name</strong> - displayed in the monitor list</li>
<li><strong>Feed URL</strong> - any RSS or Atom feed URL</li>
<li><strong>Schedule</strong> - cron expression (e.g. <code>0 */4 * * *</code> = every 4 hours)</li>
<li><strong>Agent</strong> - agent to dispatch for new items</li>
<li><strong>Max items per run</strong> - cap on how many new items trigger the agent in one run (default: 5)</li>
<li><strong>Notification mode</strong> - <code>agent</code>, <code>pushover</code>, or <code>both</code></li>
</ul>
<p>
Already-seen item IDs are tracked so the same item never triggers twice. The monitor sends <code>ETag</code> / <code>If-Modified-Since</code> headers to avoid downloading unchanged feeds unnecessarily. Use the <strong>Fetch now</strong> button to force an immediate run.
@@ -362,7 +389,7 @@
<li>Expose an SSE endpoint at <code>/sse</code></li>
<li>Use <strong>SSE transport</strong> (not stdio)</li>
<li>Be compatible with <code>mcp==1.26.*</code></li>
<li>If built with Python FastMCP: use <code>uvicorn.run(mcp.sse_app(), host=..., port=...)</code> <strong>not</strong> <code>mcp.run(host=..., port=...)</code> (the latter ignores <code>host</code>/<code>port</code> in mcp 1.26)</li>
<li>If built with Python FastMCP: use <code>uvicorn.run(mcp.sse_app(), host=..., port=...)</code> - <strong>not</strong> <code>mcp.run(host=..., port=...)</code> (the latter ignores <code>host</code>/<code>port</code> in mcp 1.26)</li>
<li>If connecting from a non-localhost IP (e.g. <code>192.168.x.x</code>): disable DNS rebinding protection:
<pre>from mcp.server.transport_security import TransportSecuritySettings
mcp = FastMCP(
@@ -373,7 +400,7 @@ mcp = FastMCP(
)</pre>
Without this, the server rejects requests with a <code>421 Misdirected Request</code> error.
</li>
<li>oAI-Web connects per-call (open → use → close), <em>not</em> persistent the server must handle this gracefully</li>
<li>oAI-Web connects per-call (open → use → close), <em>not</em> persistent - the server must handle this gracefully</li>
</ul>
<h2>Adding an MCP Server</h2>
@@ -382,10 +409,10 @@ mcp = FastMCP(
<li>Click <strong>Add Server</strong></li>
<li>Enter:
<ul>
<li><strong>Name</strong> display name; also used for tool namespacing (slugified)</li>
<li><strong>URL</strong> full SSE endpoint, e.g. <code>http://192.168.1.72:8812/sse</code></li>
<li><strong>Transport</strong> select <code>sse</code></li>
<li><strong>API Key</strong> optional bearer token if the server requires authentication</li>
<li><strong>Name</strong> - display name; also used for tool namespacing (slugified)</li>
<li><strong>URL</strong> - full SSE endpoint, e.g. <code>http://192.168.1.72:8812/sse</code></li>
<li><strong>Transport</strong> - select <code>sse</code></li>
<li><strong>API Key</strong> - optional bearer token if the server requires authentication</li>
</ul>
</li>
<li>Click <strong>Save</strong></li>
@@ -395,7 +422,7 @@ mcp = FastMCP(
<h2>Tool Namespacing</h2>
<p>
A server named <code>Gitea MCP</code> (slugified: <code>gitea_mcp</code>) exposes tools as <code>mcp__gitea_mcp__list_repos</code>, <code>mcp__gitea_mcp__create_issue</code>, etc.
In the agent tool picker, the entire server appears as a single toggle enabling it grants access to all of its tools.
In the agent tool picker, the entire server appears as a single toggle - enabling it grants access to all of its tools.
</p>
<h2>Refreshing Tool Discovery</h2>
@@ -412,7 +439,7 @@ mcp = FastMCP(
<h2 id="settings-general">General <small style="font-weight:400;font-size:12px;color:var(--text-dim)">(Admin)</small></h2>
<ul>
<li><strong>Agent Control</strong>: Pause / Resume the global kill switch</li>
<li><strong>Runtime Limits</strong>: Max Tool Calls (per run) and Max Autonomous Runs per Hour stored in the credential store for live override without restart</li>
<li><strong>Runtime Limits</strong>: Max Tool Calls (per run) and Max Autonomous Runs per Hour - stored in the credential store for live override without restart</li>
<li><strong>Trusted Proxy IPs</strong>: Comma-separated IPs for <code>X-Forwarded-For</code> trust (requires restart)</li>
<li><strong>Users Base Folder</strong>: Set <code>system:users_base_folder</code> to an absolute path (e.g. <code>/data/users</code>) to enable per-user file storage. Each user's folder at <code>{base}/{username}/</code> is created automatically.</li>
<li><strong>Audit Log Retention</strong>: Set a retention period in days (0 = keep forever); manual clear available</li>
@@ -427,17 +454,17 @@ mcp = FastMCP(
<h2 id="settings-credentials">Credentials <small style="font-weight:400;font-size:12px;color:var(--text-dim)">(Admin)</small></h2>
<p>
A generic AES-256-GCM encrypted key-value store for API keys and other secrets. Keys use a <code>namespace:key</code> convention. Service-specific credentials (CalDAV, CardDAV, Pushover) are managed in their own dedicated tabs they do not appear here. See the <a href="#credentials">Credential Key Reference</a> for a full list of system keys.
A generic AES-256-GCM encrypted key-value store for API keys and other secrets. Keys use a <code>namespace:key</code> convention. Service-specific credentials (CalDAV, CardDAV, Pushover) are managed in their own dedicated tabs - they do not appear here. See the <a href="#credentials">Credential Key Reference</a> for a full list of system keys.
</p>
<h2 id="settings-dav">DAV (CalDAV &amp; CardDAV) <small style="font-weight:400;font-size:12px;color:var(--text-dim)">(Admin)</small></h2>
<p>
Configure CalDAV and CardDAV for the admin user. There is no system-wide fallback every user configures their own credentials independently via this tab (admin) or the <strong>CalDAV / CardDAV</strong> tab (regular users).
Configure CalDAV and CardDAV for the admin user. There is no system-wide fallback - every user configures their own credentials independently via this tab (admin) or the <strong>CalDAV / CardDAV</strong> tab (regular users).
</p>
<ul>
<li><strong>CalDAV</strong>: server URL, username, password, and calendar name. Bare hostnames (e.g. <code>mail.example.com</code>) are accepted <code>https://</code> is prepended automatically.</li>
<li><strong>CalDAV</strong>: server URL, username, password, and calendar name. Bare hostnames (e.g. <code>mail.example.com</code>) are accepted - <code>https://</code> is prepended automatically.</li>
<li><strong>CardDAV</strong>: tick <em>Same server as CalDAV</em> to reuse the same credentials, or enter a separate URL, username, and password. The SOGo URL pattern (<code>/SOGo/dav/{user}/Contacts/personal/</code>) is built automatically.</li>
<li><strong>Allow contact writes</strong>: when enabled, agents can create, update, and delete contacts (not just read them). This is per-user enabling it for your account does not affect other users.</li>
<li><strong>Allow contact writes</strong>: when enabled, agents can create, update, and delete contacts (not just read them). This is per-user - enabling it for your account does not affect other users.</li>
<li><strong>Test buttons</strong>: verify CalDAV and CardDAV connectivity without saving.</li>
</ul>
@@ -446,7 +473,7 @@ mcp = FastMCP(
Pushover sends push notifications to iOS and Android devices.
</p>
<ul>
<li><strong>App Token</strong>: registered once at <a href="https://pushover.net" target="_blank">pushover.net</a> for this oAI-Web installation. Shared by all users they cannot see or change it.</li>
<li><strong>App Token</strong>: registered once at <a href="https://pushover.net" target="_blank">pushover.net</a> for this oAI-Web installation. Shared by all users - they cannot see or change it.</li>
<li><strong>User Key</strong>: the admin's personal Pushover user key, shown on your pushover.net dashboard. Each user sets their own User Key in <strong>Settings → Pushover</strong>.</li>
</ul>
{% endif %}
@@ -457,7 +484,7 @@ mcp = FastMCP(
</p>
<ul>
<li><strong>Trigger Rules</strong>: keyword phrases that, when matched in an incoming email subject/body, dispatch a specific agent and optionally send an auto-reply</li>
<li>Matching is case-insensitive and order-independent all tokens in the phrase must appear somewhere in the message</li>
<li>Matching is case-insensitive and order-independent - all tokens in the phrase must appear somewhere in the message</li>
</ul>
<h2 id="settings-emailaccounts">Email Accounts</h2>
@@ -491,10 +518,10 @@ mcp = FastMCP(
{% if not (current_user and current_user.is_admin) %}
<h2 id="settings-caldav">CalDAV / CardDAV</h2>
<p>
Configure your personal CalDAV and CardDAV connection. There is no system-wide fallback if you don't configure it, the tools are unavailable to you.
Configure your personal CalDAV and CardDAV connection. There is no system-wide fallback - if you don't configure it, the tools are unavailable to you.
</p>
<ul>
<li><strong>CalDAV</strong>: server URL, username, password, and calendar name. Bare hostnames are accepted <code>https://</code> is added automatically.</li>
<li><strong>CalDAV</strong>: server URL, username, password, and calendar name. Bare hostnames are accepted - <code>https://</code> is added automatically.</li>
<li><strong>CardDAV</strong>: tick <em>Same server as CalDAV</em> to reuse credentials, or enter separate details.</li>
<li><strong>Allow contact writes</strong>: when enabled, agents can create, update, and delete contacts.</li>
<li><strong>Test buttons</strong>: verify connectivity before saving.</li>
@@ -502,18 +529,18 @@ mcp = FastMCP(
<h2 id="settings-pushover">Pushover</h2>
<p>
Set your personal <strong>User Key</strong> to receive push notifications on your Pushover-connected devices. Your User Key is shown on your <a href="https://pushover.net" target="_blank">pushover.net</a> dashboard. The App Token (the shared application credential) is managed by the admin you only need your own User Key.
Set your personal <strong>User Key</strong> to receive push notifications on your Pushover-connected devices. Your User Key is shown on your <a href="https://pushover.net" target="_blank">pushover.net</a> dashboard. The App Token (the shared application credential) is managed by the admin - you only need your own User Key.
</p>
{% endif %}
<h2 id="settings-webhooks">Webhooks</h2>
<p>
Inbound webhooks let external services trigger agents via HTTP useful for iOS Shortcuts, GitHub actions, Home Assistant automations, or any tool that can send an HTTP request.
Inbound webhooks let external services trigger agents via HTTP - useful for iOS Shortcuts, GitHub actions, Home Assistant automations, or any tool that can send an HTTP request.
</p>
<ul>
<li><strong>Create a webhook</strong>: assign a name, description, and target agent. The secret token is shown <strong>once</strong> at creation copy it immediately. Use <em>Rotate Token</em> to generate a new one if it is ever compromised.</li>
<li><strong>Create a webhook</strong>: assign a name, description, and target agent. The secret token is shown <strong>once</strong> at creation - copy it immediately. Use <em>Rotate Token</em> to generate a new one if it is ever compromised.</li>
<li><strong>Trigger via POST</strong>: <code>POST /webhook/{token}</code> with body <code>{"message": "..."}</code></li>
<li><strong>Trigger via GET</strong>: <code>GET /webhook/{token}?q=your+message</code> useful for iOS Shortcuts URL actions</li>
<li><strong>Trigger via GET</strong>: <code>GET /webhook/{token}?q=your+message</code> - useful for iOS Shortcuts URL actions</li>
<li><strong>Enable/disable</strong>: toggle a webhook on/off without deleting it</li>
</ul>
<p>The <strong>Outbound Targets</strong> section (same tab) manages named URLs that agents can send JSON payloads to via the <code>webhook</code> tool.</p>
@@ -527,11 +554,26 @@ mcp = FastMCP(
<li><strong>Two-Factor Authentication (TOTP)</strong>: enable/disable TOTP-based MFA. On setup, a QR code is shown to scan with any authenticator app (e.g. Aegis, Google Authenticator). Once enabled, every login requires a 6-digit code.</li>
<li><strong>Data Folder</strong>: shows the path of your auto-provisioned personal folder (set by admin via <code>system:users_base_folder</code>). This folder is where the Files page browses and where agent memory files are stored.</li>
<li><strong>Telegram Bot Token</strong>: per-user Telegram bot token (optional). Overrides the global token for your sessions.</li>
<li><strong>SSH Key</strong>: generate a personal ed25519 SSH key pair stored in your data folder. See below.</li>
</ul>
<h3 id="settings-ssh-key">SSH Key</h3>
<p>
The SSH Key section lets you generate an <strong>ed25519 key pair</strong> directly from the browser. The private key is stored in your data folder (<code>{your_folder}/.ssh/id_ed25519</code>) and never leaves the server. The public key is displayed so you can copy it to any remote server's <code>~/.ssh/authorized_keys</code>.
</p>
<ul>
<li>Click <strong>Generate SSH Key</strong> to create a new key pair. The registered email address is used as the key comment.</li>
<li>If a key already exists, the button changes to <strong>Regenerate</strong> - doing so replaces the existing key pair. Any remote servers that trusted the old public key will need to be updated.</li>
<li>Use the <strong>Copy</strong> button to copy the public key to the clipboard, then paste it into <code>~/.ssh/authorized_keys</code> on the remote server.</li>
<li>Agents with bash tool access can use this key to run <code>scp</code> or <code>ssh</code> commands against remote servers that have the public key in their <code>authorized_keys</code>.</li>
</ul>
<p class="help-note">
The SSH Key section only appears when a data folder is configured for your account. The global <code>~/.ssh/known_hosts</code> file (mounted from the host system) is still used for host key verification - if you get a host key error, connect manually from the server once to accept it.
</p>
<h2 id="settings-personality">Personality</h2>
<p>
Edit <strong>SOUL.md</strong> (agent identity, values, communication style) and <strong>USER.md</strong> (owner context: name, location, preferences) directly in the browser. Changes take effect immediately no restart required.
Edit <strong>SOUL.md</strong> (agent identity, values, communication style) and <strong>USER.md</strong> (owner context: name, location, preferences) directly in the browser. Changes take effect immediately - no restart required.
Both files are injected into every system prompt in order: SOUL.md → date/time → USER.md → security rules.
</p>
@@ -563,15 +605,15 @@ mcp = FastMCP(
<h2 id="settings-apikey">API Key <small style="font-weight:400;font-size:12px;color:var(--text-dim)">(Admin)</small></h2>
<p>
Protects the REST API for external programmatic access (scripts, home automations, other services, Swagger).
The <strong>web UI always works without a key</strong> a signed session cookie is set automatically on login.
The <strong>web UI always works without a key</strong> - a signed session cookie is set automatically on login.
The API key is only required for:
</p>
<ul>
<li>External tools and scripts calling <code>/api/*</code> directly</li>
<li>Swagger UI (<a href="/docs">/docs</a>) click <strong>Authorize</strong> and enter the key</li>
<li>Swagger UI (<a href="/docs">/docs</a>) - click <strong>Authorize</strong> and enter the key</li>
</ul>
<p>
The raw key is shown <strong>once</strong> at generation time copy it to your external tool. Only a SHA-256 hash is stored server-side. Regenerating invalidates the previous key immediately.
The raw key is shown <strong>once</strong> at generation time - copy it to your external tool. Only a SHA-256 hash is stored server-side. Regenerating invalidates the previous key immediately.
</p>
<p>
Use header <code>X-API-Key: &lt;key&gt;</code> or <code>Authorization: Bearer &lt;key&gt;</code> in external requests.
@@ -606,7 +648,7 @@ mcp = FastMCP(
<h2>MFA Management</h2>
<p>
Users set up their own TOTP in <strong>Settings → Profile → Two-Factor Authentication</strong>. As admin, you can clear any user's MFA from the Users page (useful if they lose their authenticator). The <strong>Clear MFA</strong> button resets their TOTP secret they must set it up again on next login.
Users set up their own TOTP in <strong>Settings → Profile → Two-Factor Authentication</strong>. As admin, you can clear any user's MFA from the Users page (useful if they lose their authenticator). The <strong>Clear MFA</strong> button resets their TOTP secret - they must set it up again on next login.
</p>
<h2>User Filesystem Scoping</h2>
@@ -626,7 +668,7 @@ mcp = FastMCP(
<table class="help-api-table">
<thead><tr><th>Key</th><th>Description</th></tr></thead>
<tbody>
<tr><td><code>system:paused</code></td><td>Kill switch set to <code>"1"</code> to pause all agent activity</td></tr>
<tr><td><code>system:paused</code></td><td>Kill switch - set to <code>"1"</code> to pause all agent activity</td></tr>
<tr><td><code>system:max_tool_calls</code></td><td>Live override of MAX_TOOL_CALLS env var</td></tr>
<tr><td><code>system:max_autonomous_runs_per_hour</code></td><td>Live override of MAX_AUTONOMOUS_RUNS_PER_HOUR</td></tr>
<tr><td><code>system:audit_retention_days</code></td><td>Days to keep audit entries (0 = keep forever)</td></tr>
@@ -641,7 +683,7 @@ mcp = FastMCP(
<tr><td><code>system:canary_rotated_at</code></td><td>Timestamp of last canary rotation (read-only)</td></tr>
<tr><td><code>system:security_llm_screen_enabled</code></td><td>Option 3: LLM content screening enabled</td></tr>
<tr><td><code>system:security_llm_screen_model</code></td><td>Model for LLM screening (default: google/gemini-flash-1.5)</td></tr>
<tr><td><code>system:security_llm_screen_block</code></td><td>Option 3 block mode block vs flag on UNSAFE verdict</td></tr>
<tr><td><code>system:security_llm_screen_block</code></td><td>Option 3 block mode - block vs flag on UNSAFE verdict</td></tr>
<tr><td><code>system:security_output_validation_enabled</code></td><td>Option 4: output validation for inbox sessions</td></tr>
<tr><td><code>system:security_truncation_enabled</code></td><td>Option 5: content truncation</td></tr>
<tr><td><code>system:security_max_web_chars</code></td><td>Max chars from web fetch (default: 20 000)</td></tr>
@@ -650,7 +692,7 @@ mcp = FastMCP(
<tr><td><code>system:security_max_subject_chars</code></td><td>Max chars of email subject (default: 200)</td></tr>
<tr><td><code>telegram:bot_token</code></td><td>Global Telegram bot API token</td></tr>
<tr><td><code>telegram:default_agent_id</code></td><td>UUID of agent for unmatched Telegram messages</td></tr>
<tr><td><code>pushover_app_token</code></td><td>Pushover App Token managed via <strong>Settings → Pushover</strong>, not this tab</td></tr>
<tr><td><code>pushover_app_token</code></td><td>Pushover App Token - managed via <strong>Settings → Pushover</strong>, not this tab</td></tr>
<tr><td><code>brain:mcp_key</code></td><td>2nd Brain MCP authentication key</td></tr>
<tr><td><code>system:api_key_hash</code></td><td>SHA-256 hash of the external API key (raw key never stored)</td></tr>
<tr><td><code>system:api_key_created_at</code></td><td>Timestamp of last API key generation</td></tr>
@@ -705,8 +747,8 @@ mcp = FastMCP(
<table class="help-api-table">
<thead><tr><th>Method</th><th>Path</th><th>Description</th></tr></thead>
<tbody>
<tr><td><span class="http-get">GET</span></td><td><code>/api/settings/api-key</code></td><td>Returns <code>{configured: bool, created_at}</code> never returns the raw key</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/settings/api-key</code></td><td>Generate a new key returns <code>{key}</code> once only; invalidates previous key</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/settings/api-key</code></td><td>Returns <code>{configured: bool, created_at}</code> - never returns the raw key</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/settings/api-key</code></td><td>Generate a new key - returns <code>{key}</code> once only; invalidates previous key</td></tr>
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/settings/api-key</code></td><td>Revoke the current key</td></tr>
</tbody>
</table>
@@ -849,6 +891,8 @@ mcp = FastMCP(
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/my/files</code></td><td>Delete a file; param: <code>?path=</code>. Protected names (<code>memory_*</code>, <code>reasoning_*</code>) return 403.</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/files/download</code></td><td>Download a single file; param: <code>?path=</code></td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/files/download-zip</code></td><td>Download a folder as ZIP; param: <code>?path=</code></td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/files/upload</code></td><td>Upload one or more files to the user's folder; param: <code>?path=</code>, multipart form body</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/files/view</code></td><td>Return text file contents (max 512 KB); param: <code>?path=</code></td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/data-folder</code></td><td>Return the user's provisioned data folder path</td></tr>
</tbody>
</table>
@@ -864,7 +908,7 @@ mcp = FastMCP(
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/theme</code></td><td>Get current theme</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/theme</code></td><td>Set theme <code>{theme_id}</code></td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/mfa/status</code></td><td>Whether MFA is enabled for the current user</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/mfa/setup/begin</code></td><td>Start MFA setup returns QR code PNG (base64) and provisioning URI</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/mfa/setup/begin</code></td><td>Start MFA setup - returns QR code PNG (base64) and provisioning URI</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/mfa/setup/confirm</code></td><td>Confirm setup with a valid TOTP code <code>{code}</code></td></tr>
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/my/mfa/disable</code></td><td>Disable MFA for the current user</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/caldav/config</code></td><td>Get per-user CalDAV &amp; CardDAV config</td></tr>
@@ -876,6 +920,8 @@ mcp = FastMCP(
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/pushover</code></td><td>Save personal User Key <code>{user_key}</code></td></tr>
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/my/pushover</code></td><td>Remove personal User Key</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/telegram/whitelisted-chats</code></td><td>List Telegram chat IDs whitelisted for the current user</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/ssh/pubkey</code></td><td>Return the user's SSH public key (or <code>{exists: false}</code> if not generated yet)</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/ssh/generate</code></td><td>Generate (or regenerate) an ed25519 SSH key pair in the user's data folder; returns the public key</td></tr>
</tbody>
</table>
</div>
@@ -886,16 +932,16 @@ mcp = FastMCP(
<thead><tr><th>Method</th><th>Path</th><th>Description</th></tr></thead>
<tbody>
<tr><td><span class="http-get">GET</span></td><td><code>/api/webhooks</code></td><td>List inbound webhook endpoints (admin)</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/webhooks</code></td><td>Create endpoint returns token once (admin)</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/webhooks</code></td><td>Create endpoint - returns token once (admin)</td></tr>
<tr><td><span class="http-put">PUT</span></td><td><code>/api/webhooks/{id}</code></td><td>Update name/description/agent/enabled (admin)</td></tr>
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/webhooks/{id}</code></td><td>Delete endpoint (admin)</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/webhooks/{id}/rotate</code></td><td>Regenerate token returns new token once (admin)</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/webhooks/{id}/rotate</code></td><td>Regenerate token - returns new token once (admin)</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/my/webhooks</code></td><td>List current user's webhook endpoints</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/my/webhooks</code></td><td>Create personal webhook endpoint</td></tr>
<tr><td><span class="http-put">PUT</span></td><td><code>/api/my/webhooks/{id}</code></td><td>Update personal webhook endpoint</td></tr>
<tr><td><span class="http-del">DELETE</span></td><td><code>/api/my/webhooks/{id}</code></td><td>Delete personal webhook endpoint</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/webhook/{token}</code></td><td>Trigger via GET param: <code>?q=message</code> (no auth)</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/webhook/{token}</code></td><td>Trigger via POST body: <code>{"message": "...", "async": true}</code> (no auth)</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/webhook/{token}</code></td><td>Trigger via GET - param: <code>?q=message</code> (no auth)</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/webhook/{token}</code></td><td>Trigger via POST - body: <code>{"message": "...", "async": true}</code> (no auth)</td></tr>
<tr><td><span class="http-get">GET</span></td><td><code>/api/webhook-targets</code></td><td>List outbound webhook targets (admin)</td></tr>
<tr><td><span class="http-post">POST</span></td><td><code>/api/webhook-targets</code></td><td>Create outbound target (admin)</td></tr>
<tr><td><span class="http-put">PUT</span></td><td><code>/api/webhook-targets/{id}</code></td><td>Update outbound target (admin)</td></tr>
@@ -962,14 +1008,14 @@ mcp = FastMCP(
<h2>Core Principle</h2>
<p>
<strong>External input is data, never instructions.</strong> Email body text, calendar content, web page content, and file contents are all passed as <em>tool results</em> they are never injected into the system prompt where they could alter {{ agent_name }}'s instructions.
<strong>External input is data, never instructions.</strong> Email body text, calendar content, web page content, and file contents are all passed as <em>tool results</em> - they are never injected into the system prompt where they could alter {{ agent_name }}'s instructions.
</p>
<h2>Three DB-Managed Whitelists</h2>
<ul>
<li><strong>Email whitelist</strong> {{ agent_name }} can only send email to addresses explicitly approved here</li>
<li><strong>Web whitelist (Tier 1)</strong> domains always accessible; subdomains included automatically</li>
<li><strong>Filesystem sandbox</strong> {{ agent_name }} can only read/write within declared directories (or a user's personal folder)</li>
<li><strong>Email whitelist</strong> - {{ agent_name }} can only send email to addresses explicitly approved here</li>
<li><strong>Web whitelist (Tier 1)</strong> - domains always accessible; subdomains included automatically</li>
<li><strong>Filesystem sandbox</strong> - {{ agent_name }} can only read/write within declared directories (or a user's personal folder)</li>
</ul>
<p>Tier 2 web access (any URL) is only available in user-initiated chat sessions, never in autonomous agent runs.</p>
@@ -980,21 +1026,21 @@ mcp = FastMCP(
<h2>Confirmation Flow</h2>
<p>
In interactive chat, any tool with side effects (send email, write/delete files, send notifications, create/delete calendar events) triggers a confirmation modal. The agent pauses until you approve or deny. Agents running headlessly skip confirmations their scope is declared at creation time.
In interactive chat, any tool with side effects (send email, write/delete files, send notifications, create/delete calendar events) triggers a confirmation modal. The agent pauses until you approve or deny. Agents running headlessly skip confirmations - their scope is declared at creation time.
</p>
<h2>Five Security Options</h2>
<ol>
<li><strong>Enhanced Sanitization</strong> removes known prompt-injection patterns from all external content before it reaches the agent</li>
<li><strong>Canary Token</strong> a daily-rotating secret in the system prompt; any tool call argument containing the canary is blocked and triggers a Pushover alert, detecting prompt-injection exfiltration attempts</li>
<li><strong>LLM Content Screening</strong> a cheap secondary model screens fetched content for malicious instructions; operates in flag or block mode</li>
<li><strong>Output Validation</strong> prevents inbox auto-reply loops by blocking outbound emails back to the triggering sender</li>
<li><strong>Content Truncation</strong> enforces maximum character limits on web fetch, email, and file content to limit the attack surface of large malicious documents</li>
<li><strong>Enhanced Sanitization</strong> - removes known prompt-injection patterns from all external content before it reaches the agent</li>
<li><strong>Canary Token</strong> - a daily-rotating secret in the system prompt; any tool call argument containing the canary is blocked and triggers a Pushover alert, detecting prompt-injection exfiltration attempts</li>
<li><strong>LLM Content Screening</strong> - a cheap secondary model screens fetched content for malicious instructions; operates in flag or block mode</li>
<li><strong>Output Validation</strong> - prevents inbox auto-reply loops by blocking outbound emails back to the triggering sender</li>
<li><strong>Content Truncation</strong> - enforces maximum character limits on web fetch, email, and file content to limit the attack surface of large malicious documents</li>
</ol>
<h2>Audit Log</h2>
<p>
Every tool call arguments, result summary, confirmation status, session ID, task ID is written to an append-only audit log. Logs are never auto-deleted unless you configure a retention period. View them at <a href="/audit">Audit Log</a>.
Every tool call - arguments, result summary, confirmation status, session ID, task ID - is written to an append-only audit log. Logs are never auto-deleted unless you configure a retention period. View them at <a href="/audit">Audit Log</a>.
</p>
<h2>Kill Switch</h2>
@@ -1004,7 +1050,7 @@ mcp = FastMCP(
<h2>No Credentials in Agent Context</h2>
<p>
API keys, passwords, and tokens are only accessed by the server-side tool implementations. The agent itself never sees a raw credential it only receives structured results (e.g. a list of calendar events, a fetched page).
API keys, passwords, and tokens are only accessed by the server-side tool implementations. The agent itself never sees a raw credential - it only receives structured results (e.g. a list of calendar events, a fetched page).
</p>
</section>
@@ -1036,16 +1082,16 @@ mcp = FastMCP(
</p>
<p>Built-in sub-commands (e.g. for keyword <code>work</code>):</p>
<ul>
<li><code>/work pause</code> temporarily pause the email account's listener</li>
<li><code>/work resume</code> resume the listener</li>
<li><code>/work status</code> show the account's current status</li>
<li><code>/work &lt;any message&gt;</code> pass the message to the handling agent</li>
<li><code>/work pause</code> - temporarily pause the email account's listener</li>
<li><code>/work resume</code> - resume the listener</li>
<li><code>/work status</code> - show the account's current status</li>
<li><code>/work &lt;any message&gt;</code> - pass the message to the handling agent</li>
</ul>
<p class="help-note">
Only the Telegram chat ID associated with the email account can use its keyword commands. Other chat IDs are rejected.
</p>
<h2>Email Inbox Trigger Accounts</h2>
<h2>Email Inbox - Trigger Accounts</h2>
<p>
Trigger accounts use IMAP IDLE for instant push notification. When a new email arrives:
</p>
@@ -1064,15 +1110,15 @@ mcp = FastMCP(
<li>Non-whitelisted sender + no trigger → <strong>silently dropped</strong> (reveals nothing to the sender)</li>
</ul>
<h2>Email Inbox Handling Accounts</h2>
<h2>Email Inbox - Handling Accounts</h2>
<p>
Handling accounts poll every 60 seconds. A dedicated AI agent reads each new email and decides how to handle it. The agent has access to:
</p>
<ul>
<li><strong>Email tool</strong> list, read, mark as read, move, create folders</li>
<li><strong>Filesystem tool</strong> scoped to the user's data folder (if configured)</li>
<li><strong>Memory files</strong> <code>memory_&lt;username&gt;.md</code> (persistent notes) and <code>reasoning_&lt;username&gt;.md</code> (append-only decision log) are injected into each run</li>
<li><strong>Telegram tool</strong> (optional) bound to the account's associated chat ID; reply messages automatically include a <code>/keyword &lt;reply&gt;</code> footer for easy follow-up</li>
<li><strong>Email tool</strong> - list, read, mark as read, move, create folders</li>
<li><strong>Filesystem tool</strong> - scoped to the user's data folder (if configured)</li>
<li><strong>Memory files</strong> - <code>memory_&lt;username&gt;.md</code> (persistent notes) and <code>reasoning_&lt;username&gt;.md</code> (append-only decision log) are injected into each run</li>
<li><strong>Telegram tool</strong> (optional) - bound to the account's associated chat ID; reply messages automatically include a <code>/keyword &lt;reply&gt;</code> footer for easy follow-up</li>
<li><strong>Pushover tool</strong> (optional, admin only)</li>
</ul>
@@ -1081,8 +1127,8 @@ mcp = FastMCP(
Both Telegram and email inbox use the same trigger-matching algorithm:
</p>
<ul>
<li><strong>Case-insensitive</strong> <code>URGENT</code> matches <code>urgent</code></li>
<li><strong>Order-independent</strong> all tokens in the trigger phrase must appear somewhere in the message, but not necessarily in sequence</li>
<li><strong>Case-insensitive</strong> - <code>URGENT</code> matches <code>urgent</code></li>
<li><strong>Order-independent</strong> - all tokens in the trigger phrase must appear somewhere in the message, but not necessarily in sequence</li>
<li>Example: trigger phrase <code>daily report</code> matches <em>"Send me the report for the daily standup"</em> but also <em>"Daily summary report please"</em></li>
</ul>
</section>