mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-24 20:09:02 +00:00
feat: add bot-docs app — Hanzo Bot documentation site
Fumadocs-based docs site migrated from OpenClaw (285 MDX files, 15 categories). Rebranded to Hanzo Bot with red theme, landing page, CI workflow. - 261 static pages (install, concepts, CLI, channels, providers, tools, gateway, etc.) - Mintlify compatibility components (CardGroup, Card, Info, Warning, Note) - CI: deploy-bot-docs.yml builds + pushes to GHCR + deploys to K8s - Static export via nginx:alpine Docker image - Deploys to docs.hanzo.bot
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
name: Deploy Bot Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev]
|
||||
paths:
|
||||
- 'apps/bot-docs/**'
|
||||
- '.github/workflows/deploy-bot-docs.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: hanzoai/bot-docs
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm build --filter=bot-docs
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: apps/bot-docs
|
||||
file: apps/bot-docs/Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||
|
||||
- name: Deploy to K8s
|
||||
uses: digitalocean/action-doctl@v2
|
||||
with:
|
||||
token: ${{ secrets.DO_API_TOKEN }}
|
||||
|
||||
- name: Rollout new image
|
||||
run: |
|
||||
doctl kubernetes cluster kubeconfig save ${{ secrets.DO_K8S_CLUSTER_ID }}
|
||||
kubectl -n hanzo set image deployment/bot-docs bot-docs=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||
kubectl -n hanzo rollout status deployment/bot-docs --timeout=120s
|
||||
@@ -0,0 +1,5 @@
|
||||
FROM nginx:alpine
|
||||
COPY out/ /usr/share/nginx/html/
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { HomeLayout } from '@hanzo/docs/ui/layouts/home';
|
||||
import { baseOptions, linkItems, logo } from '@/lib/layout.shared';
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<HomeLayout
|
||||
{...baseOptions()}
|
||||
links={linkItems}
|
||||
nav={{
|
||||
title: (
|
||||
<>
|
||||
{logo}
|
||||
<span className="font-bold">Hanzo Bot</span>
|
||||
</>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</HomeLayout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
import Link from 'next/link';
|
||||
import { cn } from '@/lib/cn';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import {
|
||||
BotIcon,
|
||||
MessageSquareIcon,
|
||||
ShieldCheckIcon,
|
||||
PlugIcon,
|
||||
ZapIcon,
|
||||
SmartphoneIcon,
|
||||
ServerIcon,
|
||||
CloudIcon,
|
||||
HeartHandshakeIcon,
|
||||
TerminalIcon,
|
||||
BrainCircuitIcon,
|
||||
GlobeIcon,
|
||||
} from 'lucide-react';
|
||||
|
||||
const headingVariants = cva('font-medium tracking-tight', {
|
||||
variants: {
|
||||
variant: {
|
||||
h1: 'text-4xl lg:text-5xl xl:text-6xl',
|
||||
h2: 'text-3xl lg:text-4xl',
|
||||
h3: 'text-xl lg:text-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex justify-center px-5 py-3 rounded-full font-medium tracking-tight transition-colors',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
primary: 'bg-brand text-brand-foreground hover:bg-brand-200',
|
||||
secondary: 'border bg-fd-secondary text-fd-secondary-foreground hover:bg-fd-accent',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'primary',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const cardVariants = cva('rounded-2xl text-sm p-6 bg-origin-border shadow-lg', {
|
||||
variants: {
|
||||
variant: {
|
||||
secondary: 'bg-brand-secondary text-brand-secondary-foreground',
|
||||
default: 'border bg-fd-card',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
});
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<main className="text-landing-foreground pt-4 pb-6 dark:text-landing-foreground-dark md:pb-12">
|
||||
{/* Hero Section */}
|
||||
<div className="relative flex min-h-[600px] h-[70vh] max-h-[900px] border rounded-2xl overflow-hidden mx-auto w-full max-w-[1400px] bg-gradient-to-br from-brand/10 via-transparent to-brand-secondary/10">
|
||||
<div className="absolute inset-0 bg-grid-pattern opacity-5" />
|
||||
<div className="flex flex-col z-2 px-4 size-full md:p-12 max-md:items-center max-md:text-center">
|
||||
<div className="flex items-center gap-2 mt-12 text-xs text-brand font-medium rounded-full p-2 border border-brand/50 w-fit">
|
||||
<BotIcon className="size-4" />
|
||||
AI-Powered Personal Assistant
|
||||
</div>
|
||||
<h1 className={cn(headingVariants({ variant: 'h1' }), 'my-8 leading-tight')}>
|
||||
<span className="text-brand">Hanzo Bot</span> — Your AI
|
||||
<br />
|
||||
on Every Platform
|
||||
</h1>
|
||||
<p className="text-lg text-fd-muted-foreground max-w-2xl mb-8">
|
||||
AI-powered personal assistant for any OS. Gateway to WhatsApp, Telegram,
|
||||
Discord, iMessage, and more. Full system access with privacy by default.
|
||||
Self-host or run on Hanzo Cloud.
|
||||
</p>
|
||||
<div className="flex flex-row items-center justify-center gap-4 flex-wrap w-fit">
|
||||
<Link href="/docs" className={cn(buttonVariants(), 'max-sm:text-sm')}>
|
||||
Get Started
|
||||
</Link>
|
||||
<Link href="/docs/install" className={cn(buttonVariants({ variant: 'secondary' }), 'max-sm:text-sm')}>
|
||||
Install Now
|
||||
</Link>
|
||||
<a
|
||||
href="https://github.com/hanzoai/bot"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className={cn(buttonVariants({ variant: 'secondary' }), 'max-sm:text-sm')}
|
||||
>
|
||||
View on GitHub
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Terminal Preview */}
|
||||
<div className="mt-12 w-full max-w-3xl mx-auto">
|
||||
<div className="bg-fd-card border rounded-xl overflow-hidden shadow-2xl">
|
||||
<div className="flex items-center gap-2 px-4 py-2 border-b bg-fd-muted/50">
|
||||
<div className="flex gap-1.5">
|
||||
<div className="size-3 rounded-full bg-red-500" />
|
||||
<div className="size-3 rounded-full bg-yellow-500" />
|
||||
<div className="size-3 rounded-full bg-green-500" />
|
||||
</div>
|
||||
<span className="text-xs text-fd-muted-foreground ml-2">terminal</span>
|
||||
</div>
|
||||
<pre className="p-4 text-sm overflow-x-auto font-mono">
|
||||
<code>{`$ curl -fsSL https://hanzo.bot/install.sh | bash
|
||||
$ hanzo-bot setup
|
||||
$ hanzo-bot gateway
|
||||
|
||||
Hanzo Bot Gateway running on 127.0.0.1:18789
|
||||
Dashboard: http://127.0.0.1:18789/
|
||||
|
||||
Channels:
|
||||
✓ WhatsApp connected (paired)
|
||||
✓ Telegram connected (@mybot)
|
||||
✓ Discord connected (Hanzo#1234)
|
||||
|
||||
Skills: 743 loaded
|
||||
Provider: anthropic (claude-sonnet-4-5-20250514)
|
||||
Memory: persistent (SQLite)
|
||||
|
||||
Ready. Send a message to get started.`}</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features Grid */}
|
||||
<div className="grid grid-cols-1 gap-10 mt-12 px-6 mx-auto w-full max-w-[1400px] md:px-12 lg:grid-cols-2">
|
||||
<p className="text-2xl tracking-tight leading-snug font-light col-span-full md:text-3xl xl:text-4xl">
|
||||
Connect to <span className="text-brand font-medium">every messaging platform</span>,{' '}
|
||||
run <span className="text-brand font-medium">743+ skills</span>, and{' '}
|
||||
keep <span className="text-brand font-medium">full control</span> of your data.
|
||||
</p>
|
||||
|
||||
{/* Channels */}
|
||||
<div className={cn(cardVariants())}>
|
||||
<MessageSquareIcon className="size-8 text-brand mb-4" />
|
||||
<h3 className={cn(headingVariants({ variant: 'h3', className: 'mb-4' }))}>
|
||||
Multi-Channel Gateway
|
||||
</h3>
|
||||
<ul className="space-y-3 text-fd-muted-foreground">
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-brand">WhatsApp</span> — full media support, group chats
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-brand">Telegram</span> — bot API and user gateway
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-brand">Discord</span> — server bots, DMs, threads
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-brand">iMessage, Signal, Slack, Matrix</span> and more
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Hanzo Cloud */}
|
||||
<div className={cn(cardVariants({ variant: 'secondary' }))}>
|
||||
<CloudIcon className="size-8 mb-4" />
|
||||
<h3 className={cn(headingVariants({ variant: 'h3', className: 'mb-4' }))}>
|
||||
Hanzo Cloud
|
||||
</h3>
|
||||
<p className="mb-4">
|
||||
Don't want to self-host? Run Hanzo Bot on Hanzo Cloud with zero config.
|
||||
Start free with a <strong>$5 credit</strong> on signup.
|
||||
</p>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li>Managed infrastructure, automatic updates</li>
|
||||
<li>Scale from hobby to enterprise</li>
|
||||
<li>25% of compute costs paid back to OSS developers</li>
|
||||
<li>OpenClaw compatible — bring your existing config</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Skills & Tools */}
|
||||
<div className={cn(cardVariants())}>
|
||||
<ZapIcon className="size-8 text-brand mb-4" />
|
||||
<h3 className={cn(headingVariants({ variant: 'h3', className: 'mb-4' }))}>
|
||||
743+ Skills
|
||||
</h3>
|
||||
<p className="text-fd-muted-foreground mb-4">
|
||||
Extensible skill system with browser automation, code execution,
|
||||
web search, file management, and hundreds of community-built skills.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div className="p-3 rounded-lg border">
|
||||
<p className="font-medium">Browser</p>
|
||||
<p className="text-xs text-fd-muted-foreground">Headless automation</p>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg border">
|
||||
<p className="font-medium">Code Exec</p>
|
||||
<p className="text-xs text-fd-muted-foreground">Sandboxed runtime</p>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg border">
|
||||
<p className="font-medium">Web Search</p>
|
||||
<p className="text-xs text-fd-muted-foreground">Brave, Google, DuckDuckGo</p>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg border">
|
||||
<p className="font-medium">Media</p>
|
||||
<p className="text-xs text-fd-muted-foreground">Images, audio, voice</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Privacy & Self-Host */}
|
||||
<div className={cn(cardVariants())}>
|
||||
<ShieldCheckIcon className="size-8 text-brand mb-4" />
|
||||
<h3 className={cn(headingVariants({ variant: 'h3', className: 'mb-4' }))}>
|
||||
Privacy by Default
|
||||
</h3>
|
||||
<p className="text-fd-muted-foreground mb-4">
|
||||
Self-host on your own hardware. All data stays local.
|
||||
Loopback-first architecture means your messages never leave your machine.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div className="p-3 rounded-lg border">
|
||||
<p className="font-medium">Local-first</p>
|
||||
<p className="text-xs text-fd-muted-foreground">Loopback by default</p>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg border">
|
||||
<p className="font-medium">Your keys</p>
|
||||
<p className="text-xs text-fd-muted-foreground">BYO API credentials</p>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg border">
|
||||
<p className="font-medium">Sandboxed</p>
|
||||
<p className="text-xs text-fd-muted-foreground">Configurable execution</p>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg border">
|
||||
<p className="font-medium">Open Source</p>
|
||||
<p className="text-xs text-fd-muted-foreground">MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* LLM Providers */}
|
||||
<div className={cn(cardVariants(), 'col-span-full')}>
|
||||
<BrainCircuitIcon className="size-8 text-brand mb-4" />
|
||||
<h3 className={cn(headingVariants({ variant: 'h3', className: 'mb-6' }))}>
|
||||
Any LLM Provider
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{[
|
||||
{ name: 'OpenAI', desc: 'GPT-5, o3, o4-mini' },
|
||||
{ name: 'Anthropic', desc: 'Claude 4.5/4.6' },
|
||||
{ name: 'Ollama', desc: 'Local models' },
|
||||
{ name: 'Qwen', desc: 'Qwen3 series' },
|
||||
{ name: 'OpenRouter', desc: '200+ models' },
|
||||
{ name: 'Bedrock', desc: 'AWS hosted' },
|
||||
].map((provider) => (
|
||||
<div
|
||||
key={provider.name}
|
||||
className="flex items-center gap-3 px-4 py-3 rounded-lg border"
|
||||
>
|
||||
<span className="font-medium">{provider.name}</span>
|
||||
<span className="text-xs text-fd-muted-foreground">{provider.desc}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* OSS Revenue Share */}
|
||||
<div className={cn(cardVariants({ variant: 'secondary' }), 'col-span-full')}>
|
||||
<HeartHandshakeIcon className="size-8 mb-4" />
|
||||
<h3 className={cn(headingVariants({ variant: 'h3', className: 'mb-4' }))}>
|
||||
25% Back to Open Source
|
||||
</h3>
|
||||
<p className="mb-6 max-w-3xl">
|
||||
Unlike traditional cloud platforms, Hanzo pays <strong>25% of compute costs</strong> back
|
||||
to OSS developers. Connect your account, share skills, and earn revenue when your
|
||||
contributions power Hanzo Bot instances across the network.
|
||||
</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="p-4 bg-black/20 rounded-lg">
|
||||
<h4 className="font-medium mb-1">Build Skills</h4>
|
||||
<p className="text-sm opacity-80">Create and publish skills to the marketplace</p>
|
||||
</div>
|
||||
<div className="p-4 bg-black/20 rounded-lg">
|
||||
<h4 className="font-medium mb-1">Connect Account</h4>
|
||||
<p className="text-sm opacity-80">Link your GitHub to track contributions</p>
|
||||
</div>
|
||||
<div className="p-4 bg-black/20 rounded-lg">
|
||||
<h4 className="font-medium mb-1">Earn Revenue</h4>
|
||||
<p className="text-sm opacity-80">Get paid when your code powers bots</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Start */}
|
||||
<div className={cn(cardVariants(), 'col-span-full')}>
|
||||
<h2 className={cn(headingVariants({ variant: 'h2', className: 'mb-6' }))}>
|
||||
Quick Start
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">Install</h4>
|
||||
<pre className="bg-fd-muted/30 rounded-lg p-3 text-xs overflow-x-auto font-mono">
|
||||
{`curl -fsSL https://hanzo.bot/install.sh | bash`}
|
||||
</pre>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">npm</h4>
|
||||
<pre className="bg-fd-muted/30 rounded-lg p-3 text-xs overflow-x-auto font-mono">
|
||||
{`npm install -g @hanzo/bot
|
||||
hanzo-bot setup`}
|
||||
</pre>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">Docker</h4>
|
||||
<pre className="bg-fd-muted/30 rounded-lg p-3 text-xs overflow-x-auto font-mono">
|
||||
{`docker run -d --name hanzo-bot \\
|
||||
ghcr.io/hanzoai/bot:latest`}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Platforms */}
|
||||
<div className={cn(cardVariants(), 'col-span-full')}>
|
||||
<GlobeIcon className="size-8 text-brand mb-4" />
|
||||
<h3 className={cn(headingVariants({ variant: 'h3', className: 'mb-6' }))}>
|
||||
Runs Everywhere
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{[
|
||||
{ name: 'macOS', desc: 'Native companion app' },
|
||||
{ name: 'Linux', desc: 'CLI + systemd' },
|
||||
{ name: 'Windows', desc: 'WSL2 supported' },
|
||||
{ name: 'iOS', desc: 'Mobile node' },
|
||||
{ name: 'Android', desc: 'Termux compatible' },
|
||||
{ name: 'Docker', desc: 'Container ready' },
|
||||
{ name: 'Fly.io', desc: 'Edge deploy' },
|
||||
{ name: 'Hanzo Cloud', desc: 'Managed hosting' },
|
||||
].map((platform) => (
|
||||
<div
|
||||
key={platform.name}
|
||||
className="flex items-center gap-3 px-4 py-3 rounded-lg border"
|
||||
>
|
||||
<span className="font-medium">{platform.name}</span>
|
||||
<span className="text-xs text-fd-muted-foreground">{platform.desc}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CTA */}
|
||||
<div className={cn(cardVariants(), 'col-span-full text-center py-12')}>
|
||||
<h2 className={cn(headingVariants({ variant: 'h2', className: 'mb-4' }))}>
|
||||
Ready to Get Started?
|
||||
</h2>
|
||||
<p className="text-fd-muted-foreground mb-8 max-w-2xl mx-auto">
|
||||
Install Hanzo Bot in under a minute. Self-host for free or launch on
|
||||
Hanzo Cloud with a $5 credit. OpenClaw compatible.
|
||||
</p>
|
||||
<div className="flex flex-row items-center justify-center gap-4">
|
||||
<Link href="/docs" className={cn(buttonVariants())}>
|
||||
Read the Docs
|
||||
</Link>
|
||||
<a
|
||||
href="https://cloud.hanzo.ai"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className={cn(buttonVariants({ variant: 'secondary' }))}
|
||||
>
|
||||
Try Hanzo Cloud
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/hanzoai/bot"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className={cn(buttonVariants({ variant: 'secondary' }))}
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { type ComponentProps, type FC, type ReactNode } from 'react';
|
||||
import { Callout } from '@hanzo/docs-base-ui/components/callout';
|
||||
import { TypeTable } from '@hanzo/docs-base-ui/components/type-table';
|
||||
import { createMetadata } from '@/lib/metadata';
|
||||
import { source } from '@/lib/source';
|
||||
import { Card, Cards } from '@hanzo/docs-base-ui/components/card';
|
||||
import Link from '@hanzo/docs-core/link';
|
||||
import { findSiblings } from '@hanzo/docs-core/page-tree';
|
||||
import { DocsBody, DocsPage, PageLastUpdate } from '@hanzo/docs-base-ui/layouts/docs/page';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { getMDXComponents } from '@/mdx-components';
|
||||
|
||||
interface DocsPageProps {
|
||||
params: Promise<{ slug?: string[] }>;
|
||||
}
|
||||
|
||||
export const revalidate = false;
|
||||
|
||||
export default async function Page(props: DocsPageProps) {
|
||||
const params = await props.params;
|
||||
const page = source.getPage(params.slug);
|
||||
|
||||
if (!page) notFound();
|
||||
|
||||
const { body: Mdx, toc, lastModified } = await page.data.load();
|
||||
|
||||
return (
|
||||
<DocsPage
|
||||
toc={toc}
|
||||
tableOfContent={{
|
||||
style: 'clerk',
|
||||
}}
|
||||
>
|
||||
<h1 className="text-[1.75em] font-semibold">{page.data.title}</h1>
|
||||
<p className="text-lg text-fd-muted-foreground mb-6">{page.data.description}</p>
|
||||
<div className="prose flex-1 text-fd-foreground/90">
|
||||
<Mdx
|
||||
components={getMDXComponents({
|
||||
a: (props) => <Link {...props} />,
|
||||
blockquote: Callout as unknown as FC<ComponentProps<'blockquote'>>,
|
||||
TypeTable,
|
||||
DocsCategory: ({ url }) => {
|
||||
return <DocsCategory url={url ?? page.url} />;
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{page.data.index ? <DocsCategory url={page.url} /> : null}
|
||||
</div>
|
||||
{lastModified && <PageLastUpdate date={lastModified} />}
|
||||
</DocsPage>
|
||||
);
|
||||
}
|
||||
|
||||
function DocsCategory({ url }: { url: string }) {
|
||||
return (
|
||||
<Cards>
|
||||
{findSiblings(source.getPageTree(), url).map((item) => {
|
||||
if (item.type === 'separator') return;
|
||||
if (item.type === 'folder') {
|
||||
if (!item.index) return;
|
||||
item = item.index;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card key={item.url} title={item.name} href={item.url}>
|
||||
{item.description}
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</Cards>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: DocsPageProps): Promise<Metadata> {
|
||||
const { slug = [] } = await props.params;
|
||||
const page = source.getPage(slug);
|
||||
if (!page)
|
||||
return createMetadata({
|
||||
title: 'Not Found',
|
||||
});
|
||||
|
||||
const description = page.data.description ?? 'Hanzo Bot Documentation';
|
||||
|
||||
return createMetadata({
|
||||
title: page.data.title,
|
||||
description,
|
||||
openGraph: {
|
||||
url: `/docs/${page.slugs.join('/')}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return source.generateParams();
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { DocsLayout } from '@hanzo/docs/ui/layouts/docs';
|
||||
import { baseOptions, linkItems, logo } from '@/lib/layout.shared';
|
||||
import { source } from '@/lib/source';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
const base = baseOptions();
|
||||
|
||||
return (
|
||||
<DocsLayout
|
||||
{...base}
|
||||
tree={source.getPageTree()}
|
||||
links={linkItems.filter((item) => item.type === 'icon')}
|
||||
nav={{
|
||||
...base.nav,
|
||||
title: (
|
||||
<>
|
||||
{logo}
|
||||
<span className="font-medium max-md:hidden">Hanzo Bot</span>
|
||||
</>
|
||||
),
|
||||
}}
|
||||
sidebar={{
|
||||
tabs: {
|
||||
transform(option, node) {
|
||||
if (!node.icon) return option;
|
||||
|
||||
return {
|
||||
...option,
|
||||
icon: (
|
||||
<div className="[&_svg]:size-full rounded-lg size-full text-brand max-md:bg-brand/10 max-md:border max-md:p-1.5">
|
||||
{node.icon}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</DocsLayout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
@import 'tailwindcss';
|
||||
@import '@hanzo/docs-base-ui/css/neutral.css';
|
||||
@import '@hanzo/docs-base-ui/css/preset.css';
|
||||
@plugin 'tailwindcss-animate';
|
||||
|
||||
@theme {
|
||||
--spacing-page: 1436px;
|
||||
--color-brand: hsl(0, 98%, 63%);
|
||||
--color-brand-foreground: white;
|
||||
--color-brand-secondary: hsl(0, 80%, 50%);
|
||||
--color-brand-secondary-foreground: white;
|
||||
--color-brand-200: hsl(0, 90%, 72%);
|
||||
|
||||
--color-landing-foreground: #1a1a2e;
|
||||
--color-landing-foreground-200: #4a4a6a;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-landing-foreground: #e4e4f0;
|
||||
--color-landing-foreground-200: #a0a0c0;
|
||||
|
||||
--color-brand: hsl(0, 98%, 63%);
|
||||
--color-brand-secondary: hsl(0, 100%, 65%);
|
||||
--color-brand-secondary-foreground: black;
|
||||
--color-brand-foreground: white;
|
||||
--color-brand-200: hsl(0, 90%, 80%);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--default-mono-font-family: var(--font-mono);
|
||||
--animate-pulse-fast: pulse 1s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
html:has(body[data-scroll-locked]) {
|
||||
scrollbar-gutter: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--color-fd-background);
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--color-fd-background: hsl(0, 0%, 2%);
|
||||
--color-fd-foreground: hsl(0, 0%, 96%);
|
||||
--color-fd-muted: hsl(0, 0%, 10%);
|
||||
--color-fd-muted-foreground: hsla(0, 0%, 70%, 0.85);
|
||||
--color-fd-popover: hsl(0, 0%, 4%);
|
||||
--color-fd-popover-foreground: hsl(0, 0%, 90%);
|
||||
--color-fd-card: hsl(0, 0%, 4%);
|
||||
--color-fd-card-foreground: hsl(0, 0%, 96%);
|
||||
--color-fd-border: hsla(0, 0%, 40%, 0.2);
|
||||
--color-fd-primary: hsl(0, 0%, 96%);
|
||||
--color-fd-primary-foreground: hsl(0, 0%, 8%);
|
||||
--color-fd-secondary: hsl(0, 0%, 12%);
|
||||
--color-fd-secondary-foreground: hsl(0, 0%, 92%);
|
||||
--color-fd-accent: hsla(0, 0%, 40%, 0.3);
|
||||
--color-fd-accent-foreground: hsl(0, 0%, 92%);
|
||||
--color-fd-ring: hsl(0, 0%, 55%);
|
||||
--color-fd-overlay: hsla(0, 0%, 0%, 0.3);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-fd-background: hsl(0, 0%, 2%);
|
||||
--color-fd-foreground: hsl(0, 0%, 96%);
|
||||
--color-fd-muted: hsl(0, 0%, 10%);
|
||||
--color-fd-muted-foreground: hsla(0, 0%, 70%, 0.85);
|
||||
--color-fd-popover: hsl(0, 0%, 4%);
|
||||
--color-fd-popover-foreground: hsl(0, 0%, 90%);
|
||||
--color-fd-card: hsl(0, 0%, 4%);
|
||||
--color-fd-card-foreground: hsl(0, 0%, 96%);
|
||||
--color-fd-border: hsla(0, 0%, 40%, 0.2);
|
||||
--color-fd-primary: hsl(0, 0%, 96%);
|
||||
--color-fd-primary-foreground: hsl(0, 0%, 8%);
|
||||
--color-fd-secondary: hsl(0, 0%, 12%);
|
||||
--color-fd-secondary-foreground: hsl(0, 0%, 92%);
|
||||
--color-fd-accent: hsla(0, 0%, 40%, 0.3);
|
||||
--color-fd-accent-foreground: hsl(0, 0%, 92%);
|
||||
--color-fd-ring: hsl(0, 0%, 55%);
|
||||
--color-fd-overlay: hsla(0, 0%, 0%, 0.3);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import { ThemeProvider } from 'next-themes';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export function Body({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<body className="antialiased">
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import './global.css';
|
||||
import type { Viewport } from 'next';
|
||||
import { baseUrl, createMetadata } from '@/lib/metadata';
|
||||
import { Body } from '@/app/layout.client';
|
||||
import { Provider } from './provider';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Geist, Geist_Mono } from 'next/font/google';
|
||||
import { TreeContextProvider } from '@hanzo/docs/ui/contexts/tree';
|
||||
import { source } from '@/lib/source';
|
||||
import { NextProvider } from '@hanzo/docs/core/framework/next';
|
||||
|
||||
export const metadata = createMetadata({
|
||||
title: {
|
||||
template: '%s | Hanzo Bot',
|
||||
default: 'Hanzo Bot - AI-Powered Personal Assistant',
|
||||
},
|
||||
description: 'AI-powered personal assistant for any OS. WhatsApp, Telegram, Discord, iMessage gateway with full system access and privacy by default.',
|
||||
metadataBase: baseUrl,
|
||||
});
|
||||
|
||||
const geist = Geist({
|
||||
variable: '--font-sans',
|
||||
subsets: ['latin'],
|
||||
});
|
||||
|
||||
const mono = Geist_Mono({
|
||||
variable: '--font-mono',
|
||||
subsets: ['latin'],
|
||||
});
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: [
|
||||
{ media: '(prefers-color-scheme: dark)', color: '#0A0A0A' },
|
||||
{ media: '(prefers-color-scheme: light)', color: '#0A0A0A' },
|
||||
],
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang="en" className={`${geist.variable} ${mono.variable}`} suppressHydrationWarning>
|
||||
<Body>
|
||||
<NextProvider>
|
||||
<TreeContextProvider tree={source.getPageTree()}>
|
||||
<Provider>{children}</Provider>
|
||||
</TreeContextProvider>
|
||||
</NextProvider>
|
||||
</Body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import { RootProvider } from '@hanzo/docs/ui/provider/base';
|
||||
import type { ReactNode } from 'react';
|
||||
import { TooltipProvider } from '@radix-ui/react-tooltip';
|
||||
|
||||
export function Provider({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<RootProvider theme={{ defaultTheme: 'dark', enableSystem: false }}>
|
||||
<TooltipProvider>
|
||||
{children}
|
||||
</TooltipProvider>
|
||||
</RootProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { MetadataRoute } from 'next';
|
||||
import { source } from '@/lib/source';
|
||||
import { baseUrl } from '@/lib/metadata';
|
||||
|
||||
export const dynamic = 'force-static';
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const url = (path: string) => new URL(path, baseUrl).toString();
|
||||
|
||||
return [
|
||||
{
|
||||
url: url('/'),
|
||||
changeFrequency: 'monthly',
|
||||
priority: 1,
|
||||
},
|
||||
{
|
||||
url: url('/docs'),
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.9,
|
||||
},
|
||||
...source.getPages().map((page) => ({
|
||||
url: url(page.url),
|
||||
lastModified: page.data.lastModified
|
||||
? new Date(page.data.lastModified)
|
||||
: undefined,
|
||||
changeFrequency: 'weekly' as const,
|
||||
priority: 0.8,
|
||||
})),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: "Auth monitoring"
|
||||
description: "Monitor OAuth expiry for model providers"
|
||||
---
|
||||
|
||||
Bot exposes OAuth expiry health via `bot models status`. Use that for
|
||||
automation and alerting; scripts are optional extras for phone workflows.
|
||||
|
||||
## Preferred: CLI check (portable)
|
||||
|
||||
```bash
|
||||
bot models status --check
|
||||
```
|
||||
|
||||
Exit codes:
|
||||
- `0`: OK
|
||||
- `1`: expired or missing credentials
|
||||
- `2`: expiring soon (within 24h)
|
||||
|
||||
This works in cron/systemd and requires no extra scripts.
|
||||
|
||||
## Optional scripts (ops / phone workflows)
|
||||
|
||||
These live under `scripts/` and are **optional**. They assume SSH access to the
|
||||
gateway host and are tuned for systemd + Termux.
|
||||
|
||||
- `scripts/claude-auth-status.sh` now uses `bot models status --json` as the
|
||||
source of truth (falling back to direct file reads if the CLI is unavailable),
|
||||
so keep `bot` on `PATH` for timers.
|
||||
- `scripts/auth-monitor.sh`: cron/systemd timer target; sends alerts (ntfy or phone).
|
||||
- `scripts/systemd/bot-auth-monitor.{service,timer}`: systemd user timer.
|
||||
- `scripts/claude-auth-status.sh`: Claude Code + Bot auth checker (full/json/simple).
|
||||
- `scripts/mobile-reauth.sh`: guided re‑auth flow over SSH.
|
||||
- `scripts/termux-quick-auth.sh`: one‑tap widget status + open auth URL.
|
||||
- `scripts/termux-auth-widget.sh`: full guided widget flow.
|
||||
- `scripts/termux-sync-widget.sh`: sync Claude Code creds → Bot.
|
||||
|
||||
If you don’t need phone automation or systemd timers, skip these scripts.
|
||||
@@ -0,0 +1,282 @@
|
||||
---
|
||||
title: "Cron jobs (Gateway scheduler)"
|
||||
description: "Cron jobs + wakeups for the Gateway scheduler"
|
||||
---
|
||||
|
||||
> **Cron vs Heartbeat?** See [Cron vs Heartbeat](/automation/cron-vs-heartbeat) for guidance on when to use each.
|
||||
|
||||
Cron is the Gateway’s built-in scheduler. It persists jobs, wakes the agent at
|
||||
the right time, and can optionally deliver output back to a chat.
|
||||
|
||||
If you want *“run this every morning”* or *“poke the agent in 20 minutes”*,
|
||||
cron is the mechanism.
|
||||
|
||||
## TL;DR
|
||||
- Cron runs **inside the Gateway** (not inside the model).
|
||||
- Jobs persist under `~/.bot/cron/` so restarts don’t lose schedules.
|
||||
- Two execution styles:
|
||||
- **Main session**: enqueue a system event, then run on the next heartbeat.
|
||||
- **Isolated**: run a dedicated agent turn in `cron:<jobId>`, optionally deliver output.
|
||||
- Wakeups are first-class: a job can request “wake now” vs “next heartbeat”.
|
||||
|
||||
## Beginner-friendly overview
|
||||
Think of a cron job as: **when** to run + **what** to do.
|
||||
|
||||
1) **Choose a schedule**
|
||||
- One-shot reminder → `schedule.kind = "at"` (CLI: `--at`)
|
||||
- Repeating job → `schedule.kind = "every"` or `schedule.kind = "cron"`
|
||||
- If your ISO timestamp omits a timezone, it is treated as **UTC**.
|
||||
|
||||
2) **Choose where it runs**
|
||||
- `sessionTarget: "main"` → run during the next heartbeat with main context.
|
||||
- `sessionTarget: "isolated"` → run a dedicated agent turn in `cron:<jobId>`.
|
||||
|
||||
3) **Choose the payload**
|
||||
- Main session → `payload.kind = "systemEvent"`
|
||||
- Isolated session → `payload.kind = "agentTurn"`
|
||||
|
||||
Optional: `deleteAfterRun: true` removes successful one-shot jobs from the store.
|
||||
|
||||
## Concepts
|
||||
|
||||
### Jobs
|
||||
A cron job is a stored record with:
|
||||
- a **schedule** (when it should run),
|
||||
- a **payload** (what it should do),
|
||||
- optional **delivery** (where output should be sent).
|
||||
- optional **agent binding** (`agentId`): run the job under a specific agent; if
|
||||
missing or unknown, the gateway falls back to the default agent.
|
||||
|
||||
Jobs are identified by a stable `jobId` (used by CLI/Gateway APIs).
|
||||
In agent tool calls, `jobId` is canonical; legacy `id` is accepted for compatibility.
|
||||
Jobs can optionally auto-delete after a successful one-shot run via `deleteAfterRun: true`.
|
||||
|
||||
### Schedules
|
||||
Cron supports three schedule kinds:
|
||||
- `at`: one-shot timestamp (ms since epoch). Gateway accepts ISO 8601 and coerces to UTC.
|
||||
- `every`: fixed interval (ms).
|
||||
- `cron`: 5-field cron expression with optional IANA timezone.
|
||||
|
||||
Cron expressions use `croner`. If a timezone is omitted, the Gateway host’s
|
||||
local timezone is used.
|
||||
|
||||
### Main vs isolated execution
|
||||
|
||||
#### Main session jobs (system events)
|
||||
Main jobs enqueue a system event and optionally wake the heartbeat runner.
|
||||
They must use `payload.kind = "systemEvent"`.
|
||||
|
||||
- `wakeMode: "next-heartbeat"` (default): event waits for the next scheduled heartbeat.
|
||||
- `wakeMode: "now"`: event triggers an immediate heartbeat run.
|
||||
|
||||
This is the best fit when you want the normal heartbeat prompt + main-session context.
|
||||
See [Heartbeat](/gateway/heartbeat).
|
||||
|
||||
#### Isolated jobs (dedicated cron sessions)
|
||||
Isolated jobs run a dedicated agent turn in session `cron:<jobId>`.
|
||||
|
||||
Key behaviors:
|
||||
- Prompt is prefixed with `[cron:<jobId> <job name>]` for traceability.
|
||||
- Each run starts a **fresh session id** (no prior conversation carry-over).
|
||||
- A summary is posted to the main session (prefix `Cron`, configurable).
|
||||
- `wakeMode: "now"` triggers an immediate heartbeat after posting the summary.
|
||||
- If `payload.deliver: true`, output is delivered to a channel; otherwise it stays internal.
|
||||
|
||||
Use isolated jobs for noisy, frequent, or "background chores" that shouldn't spam
|
||||
your main chat history.
|
||||
|
||||
### Payload shapes (what runs)
|
||||
Two payload kinds are supported:
|
||||
- `systemEvent`: main-session only, routed through the heartbeat prompt.
|
||||
- `agentTurn`: isolated-session only, runs a dedicated agent turn.
|
||||
|
||||
Common `agentTurn` fields:
|
||||
- `message`: required text prompt.
|
||||
- `model` / `thinking`: optional overrides (see below).
|
||||
- `timeoutSeconds`: optional timeout override.
|
||||
- `deliver`: `true` to send output to a channel target.
|
||||
- `channel`: `last` or a specific channel.
|
||||
- `to`: channel-specific target (phone/chat/channel id).
|
||||
- `bestEffortDeliver`: avoid failing the job if delivery fails.
|
||||
|
||||
Isolation options (only for `session=isolated`):
|
||||
- `postToMainPrefix` (CLI: `--post-prefix`): prefix for the system event in main.
|
||||
- `postToMainMode`: `summary` (default) or `full`.
|
||||
- `postToMainMaxChars`: max chars when `postToMainMode=full` (default 8000).
|
||||
|
||||
### Model and thinking overrides
|
||||
Isolated jobs (`agentTurn`) can override the model and thinking level:
|
||||
- `model`: Provider/model string (e.g., `anthropic/claude-sonnet-4-20250514`) or alias (e.g., `opus`)
|
||||
- `thinking`: Thinking level (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`; GPT-5.2 + Codex models only)
|
||||
|
||||
Note: You can set `model` on main-session jobs too, but it changes the shared main
|
||||
session model. We recommend model overrides only for isolated jobs to avoid
|
||||
unexpected context shifts.
|
||||
|
||||
Resolution priority:
|
||||
1. Job payload override (highest)
|
||||
2. Hook-specific defaults (e.g., `hooks.gmail.model`)
|
||||
3. Agent config default
|
||||
|
||||
### Delivery (channel + target)
|
||||
Isolated jobs can deliver output to a channel. The job payload can specify:
|
||||
- `channel`: `whatsapp` / `telegram` / `discord` / `slack` / `mattermost` (plugin) / `signal` / `imessage` / `last`
|
||||
- `to`: channel-specific recipient target
|
||||
|
||||
If `channel` or `to` is omitted, cron can fall back to the main session’s “last route”
|
||||
(the last place the agent replied).
|
||||
|
||||
Delivery notes:
|
||||
- If `to` is set, cron auto-delivers the agent’s final output even if `deliver` is omitted.
|
||||
- Use `deliver: true` when you want last-route delivery without an explicit `to`.
|
||||
- Use `deliver: false` to keep output internal even if a `to` is present.
|
||||
|
||||
Target format reminders:
|
||||
- Slack/Discord/Mattermost (plugin) targets should use explicit prefixes (e.g. `channel:<id>`, `user:<id>`) to avoid ambiguity.
|
||||
- Telegram topics should use the `:topic:` form (see below).
|
||||
|
||||
#### Telegram delivery targets (topics / forum threads)
|
||||
Telegram supports forum topics via `message_thread_id`. For cron delivery, you can encode
|
||||
the topic/thread into the `to` field:
|
||||
|
||||
- `-1001234567890` (chat id only)
|
||||
- `-1001234567890:topic:123` (preferred: explicit topic marker)
|
||||
- `-1001234567890:123` (shorthand: numeric suffix)
|
||||
|
||||
Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:
|
||||
- `telegram:group:-1001234567890:topic:123`
|
||||
|
||||
## Storage & history
|
||||
- Job store: `~/.bot/cron/jobs.json` (Gateway-managed JSON).
|
||||
- Run history: `~/.bot/cron/runs/<jobId>.jsonl` (JSONL, auto-pruned).
|
||||
- Override store path: `cron.store` in config.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json5
|
||||
{
|
||||
cron: {
|
||||
enabled: true, // default true
|
||||
store: "~/.bot/cron/jobs.json",
|
||||
maxConcurrentRuns: 1 // default 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Disable cron entirely:
|
||||
- `cron.enabled: false` (config)
|
||||
- `BOT_SKIP_CRON=1` (env)
|
||||
|
||||
## CLI quickstart
|
||||
|
||||
One-shot reminder (UTC ISO, auto-delete after success):
|
||||
```bash
|
||||
bot cron add \
|
||||
--name "Send reminder" \
|
||||
--at "2026-01-12T18:00:00Z" \
|
||||
--session main \
|
||||
--system-event "Reminder: submit expense report." \
|
||||
--wake now \
|
||||
--delete-after-run
|
||||
```
|
||||
|
||||
One-shot reminder (main session, wake immediately):
|
||||
```bash
|
||||
bot cron add \
|
||||
--name "Calendar check" \
|
||||
--at "20m" \
|
||||
--session main \
|
||||
--system-event "Next heartbeat: check calendar." \
|
||||
--wake now
|
||||
```
|
||||
|
||||
Recurring isolated job (deliver to WhatsApp):
|
||||
```bash
|
||||
bot cron add \
|
||||
--name "Morning status" \
|
||||
--cron "0 7 * * *" \
|
||||
--tz "America/Los_Angeles" \
|
||||
--session isolated \
|
||||
--message "Summarize inbox + calendar for today." \
|
||||
--deliver \
|
||||
--channel whatsapp \
|
||||
--to "+15551234567"
|
||||
```
|
||||
|
||||
Recurring isolated job (deliver to a Telegram topic):
|
||||
```bash
|
||||
bot cron add \
|
||||
--name "Nightly summary (topic)" \
|
||||
--cron "0 22 * * *" \
|
||||
--tz "America/Los_Angeles" \
|
||||
--session isolated \
|
||||
--message "Summarize today; send to the nightly topic." \
|
||||
--deliver \
|
||||
--channel telegram \
|
||||
--to "-1001234567890:topic:123"
|
||||
```
|
||||
|
||||
Isolated job with model and thinking override:
|
||||
```bash
|
||||
bot cron add \
|
||||
--name "Deep analysis" \
|
||||
--cron "0 6 * * 1" \
|
||||
--tz "America/Los_Angeles" \
|
||||
--session isolated \
|
||||
--message "Weekly deep analysis of project progress." \
|
||||
--model "opus" \
|
||||
--thinking high \
|
||||
--deliver \
|
||||
--channel whatsapp \
|
||||
--to "+15551234567"
|
||||
|
||||
Agent selection (multi-agent setups):
|
||||
```bash
|
||||
# Pin a job to agent "ops" (falls back to default if that agent is missing)
|
||||
bot cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --message "Check ops queue" --agent ops
|
||||
|
||||
# Switch or clear the agent on an existing job
|
||||
bot cron edit <jobId> --agent ops
|
||||
bot cron edit <jobId> --clear-agent
|
||||
```
|
||||
```
|
||||
|
||||
Manual run (debug):
|
||||
```bash
|
||||
bot cron run <jobId> --force
|
||||
```
|
||||
|
||||
Edit an existing job (patch fields):
|
||||
```bash
|
||||
bot cron edit <jobId> \
|
||||
--message "Updated prompt" \
|
||||
--model "opus" \
|
||||
--thinking low
|
||||
```
|
||||
|
||||
Run history:
|
||||
```bash
|
||||
bot cron runs --id <jobId> --limit 50
|
||||
```
|
||||
|
||||
Immediate system event without creating a job:
|
||||
```bash
|
||||
bot system event --mode now --text "Next heartbeat: check battery."
|
||||
```
|
||||
|
||||
## Gateway API surface
|
||||
- `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove`
|
||||
- `cron.run` (force or due), `cron.runs`
|
||||
For immediate system events without a job, use [`bot system event`](/cli/system).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### “Nothing runs”
|
||||
- Check cron is enabled: `cron.enabled` and `BOT_SKIP_CRON`.
|
||||
- Check the Gateway is running continuously (cron runs inside the Gateway process).
|
||||
- For `cron` schedules: confirm timezone (`--tz`) vs the host timezone.
|
||||
|
||||
### Telegram delivers to the wrong place
|
||||
- For forum topics, use `-100…:topic:<id>` so it’s explicit and unambiguous.
|
||||
- If you see `telegram:...` prefixes in logs or stored “last route” targets, that’s normal;
|
||||
cron delivery accepts them and still parses topic IDs correctly.
|
||||
@@ -0,0 +1,270 @@
|
||||
---
|
||||
title: "Cron vs Heartbeat: When to Use Each"
|
||||
description: "Guidance for choosing between heartbeat and cron jobs for automation"
|
||||
---
|
||||
|
||||
Both heartbeats and cron jobs let you run tasks on a schedule. This guide helps you choose the right mechanism for your use case.
|
||||
|
||||
## Quick Decision Guide
|
||||
|
||||
| Use Case | Recommended | Why |
|
||||
|----------|-------------|-----|
|
||||
| Check inbox every 30 min | Heartbeat | Batches with other checks, context-aware |
|
||||
| Send daily report at 9am sharp | Cron (isolated) | Exact timing needed |
|
||||
| Monitor calendar for upcoming events | Heartbeat | Natural fit for periodic awareness |
|
||||
| Run weekly deep analysis | Cron (isolated) | Standalone task, can use different model |
|
||||
| Remind me in 20 minutes | Cron (main, `--at`) | One-shot with precise timing |
|
||||
| Background project health check | Heartbeat | Piggybacks on existing cycle |
|
||||
|
||||
## Heartbeat: Periodic Awareness
|
||||
|
||||
Heartbeats run in the **main session** at a regular interval (default: 30 min). They're designed for the agent to check on things and surface anything important.
|
||||
|
||||
### When to use heartbeat
|
||||
|
||||
- **Multiple periodic checks**: Instead of 5 separate cron jobs checking inbox, calendar, weather, notifications, and project status, a single heartbeat can batch all of these.
|
||||
- **Context-aware decisions**: The agent has full main-session context, so it can make smart decisions about what's urgent vs. what can wait.
|
||||
- **Conversational continuity**: Heartbeat runs share the same session, so the agent remembers recent conversations and can follow up naturally.
|
||||
- **Low-overhead monitoring**: One heartbeat replaces many small polling tasks.
|
||||
|
||||
### Heartbeat advantages
|
||||
|
||||
- **Batches multiple checks**: One agent turn can review inbox, calendar, and notifications together.
|
||||
- **Reduces API calls**: A single heartbeat is cheaper than 5 isolated cron jobs.
|
||||
- **Context-aware**: The agent knows what you've been working on and can prioritize accordingly.
|
||||
- **Smart suppression**: If nothing needs attention, the agent replies `HEARTBEAT_OK` and no message is delivered.
|
||||
- **Natural timing**: Drifts slightly based on queue load, which is fine for most monitoring.
|
||||
|
||||
### Heartbeat example: HEARTBEAT.md checklist
|
||||
|
||||
```md
|
||||
# Heartbeat checklist
|
||||
|
||||
- Check email for urgent messages
|
||||
- Review calendar for events in next 2 hours
|
||||
- If a background task finished, summarize results
|
||||
- If idle for 8+ hours, send a brief check-in
|
||||
```
|
||||
|
||||
The agent reads this on each heartbeat and handles all items in one turn.
|
||||
|
||||
### Configuring heartbeat
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: {
|
||||
every: "30m", // interval
|
||||
target: "last", // where to deliver alerts
|
||||
activeHours: { start: "08:00", end: "22:00" } // optional
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See [Heartbeat](/gateway/heartbeat) for full configuration.
|
||||
|
||||
## Cron: Precise Scheduling
|
||||
|
||||
Cron jobs run at **exact times** and can run in isolated sessions without affecting main context.
|
||||
|
||||
### When to use cron
|
||||
|
||||
- **Exact timing required**: "Send this at 9:00 AM every Monday" (not "sometime around 9").
|
||||
- **Standalone tasks**: Tasks that don't need conversational context.
|
||||
- **Different model/thinking**: Heavy analysis that warrants a more powerful model.
|
||||
- **One-shot reminders**: "Remind me in 20 minutes" with `--at`.
|
||||
- **Noisy/frequent tasks**: Tasks that would clutter main session history.
|
||||
- **External triggers**: Tasks that should run independently of whether the agent is otherwise active.
|
||||
|
||||
### Cron advantages
|
||||
|
||||
- **Exact timing**: 5-field cron expressions with timezone support.
|
||||
- **Session isolation**: Runs in `cron:<jobId>` without polluting main history.
|
||||
- **Model overrides**: Use a cheaper or more powerful model per job.
|
||||
- **Delivery control**: Can deliver directly to a channel; still posts a summary to main by default (configurable).
|
||||
- **No agent context needed**: Runs even if main session is idle or compacted.
|
||||
- **One-shot support**: `--at` for precise future timestamps.
|
||||
|
||||
### Cron example: Daily morning briefing
|
||||
|
||||
```bash
|
||||
bot cron add \
|
||||
--name "Morning briefing" \
|
||||
--cron "0 7 * * *" \
|
||||
--tz "America/New_York" \
|
||||
--session isolated \
|
||||
--message "Generate today's briefing: weather, calendar, top emails, news summary." \
|
||||
--model opus \
|
||||
--deliver \
|
||||
--channel whatsapp \
|
||||
--to "+15551234567"
|
||||
```
|
||||
|
||||
This runs at exactly 7:00 AM New York time, uses Opus for quality, and delivers directly to WhatsApp.
|
||||
|
||||
### Cron example: One-shot reminder
|
||||
|
||||
```bash
|
||||
bot cron add \
|
||||
--name "Meeting reminder" \
|
||||
--at "20m" \
|
||||
--session main \
|
||||
--system-event "Reminder: standup meeting starts in 10 minutes." \
|
||||
--wake now \
|
||||
--delete-after-run
|
||||
```
|
||||
|
||||
See [Cron jobs](/automation/cron-jobs) for full CLI reference.
|
||||
|
||||
## Decision Flowchart
|
||||
|
||||
```
|
||||
Does the task need to run at an EXACT time?
|
||||
YES -> Use cron
|
||||
NO -> Continue...
|
||||
|
||||
Does the task need isolation from main session?
|
||||
YES -> Use cron (isolated)
|
||||
NO -> Continue...
|
||||
|
||||
Can this task be batched with other periodic checks?
|
||||
YES -> Use heartbeat (add to HEARTBEAT.md)
|
||||
NO -> Use cron
|
||||
|
||||
Is this a one-shot reminder?
|
||||
YES -> Use cron with --at
|
||||
NO -> Continue...
|
||||
|
||||
Does it need a different model or thinking level?
|
||||
YES -> Use cron (isolated) with --model/--thinking
|
||||
NO -> Use heartbeat
|
||||
```
|
||||
|
||||
## Combining Both
|
||||
|
||||
The most efficient setup uses **both**:
|
||||
|
||||
1. **Heartbeat** handles routine monitoring (inbox, calendar, notifications) in one batched turn every 30 minutes.
|
||||
2. **Cron** handles precise schedules (daily reports, weekly reviews) and one-shot reminders.
|
||||
|
||||
### Example: Efficient automation setup
|
||||
|
||||
**HEARTBEAT.md** (checked every 30 min):
|
||||
```md
|
||||
# Heartbeat checklist
|
||||
- Scan inbox for urgent emails
|
||||
- Check calendar for events in next 2h
|
||||
- Review any pending tasks
|
||||
- Light check-in if quiet for 8+ hours
|
||||
```
|
||||
|
||||
**Cron jobs** (precise timing):
|
||||
```bash
|
||||
# Daily morning briefing at 7am
|
||||
bot cron add --name "Morning brief" --cron "0 7 * * *" --session isolated --message "..." --deliver
|
||||
|
||||
# Weekly project review on Mondays at 9am
|
||||
bot cron add --name "Weekly review" --cron "0 9 * * 1" --session isolated --message "..." --model opus
|
||||
|
||||
# One-shot reminder
|
||||
bot cron add --name "Call back" --at "2h" --session main --system-event "Call back the client" --wake now
|
||||
```
|
||||
|
||||
|
||||
## Lobster: Deterministic workflows with approvals
|
||||
|
||||
Lobster is the workflow runtime for **multi-step tool pipelines** that need deterministic execution and explicit approvals.
|
||||
Use it when the task is more than a single agent turn, and you want a resumable workflow with human checkpoints.
|
||||
|
||||
### When Lobster fits
|
||||
|
||||
- **Multi-step automation**: You need a fixed pipeline of tool calls, not a one-off prompt.
|
||||
- **Approval gates**: Side effects should pause until you approve, then resume.
|
||||
- **Resumable runs**: Continue a paused workflow without re-running earlier steps.
|
||||
|
||||
### How it pairs with heartbeat and cron
|
||||
|
||||
- **Heartbeat/cron** decide *when* a run happens.
|
||||
- **Lobster** defines *what steps* happen once the run starts.
|
||||
|
||||
For scheduled workflows, use cron or heartbeat to trigger an agent turn that calls Lobster.
|
||||
For ad-hoc workflows, call Lobster directly.
|
||||
|
||||
### Operational notes (from the code)
|
||||
|
||||
- Lobster runs as a **local subprocess** (`lobster` CLI) in tool mode and returns a **JSON envelope**.
|
||||
- If the tool returns `needs_approval`, you resume with a `resumeToken` and `approve` flag.
|
||||
- The tool is an **optional plugin**; you must allowlist `lobster` in `tools.allow`.
|
||||
- If you pass `lobsterPath`, it must be an **absolute path**.
|
||||
|
||||
See [Lobster](/tools/lobster) for full usage and examples.
|
||||
|
||||
## Main Session vs Isolated Session
|
||||
|
||||
Both heartbeat and cron can interact with the main session, but differently:
|
||||
|
||||
| | Heartbeat | Cron (main) | Cron (isolated) |
|
||||
|---|---|---|---|
|
||||
| Session | Main | Main (via system event) | `cron:<jobId>` |
|
||||
| History | Shared | Shared | Fresh each run |
|
||||
| Context | Full | Full | None (starts clean) |
|
||||
| Model | Main session model | Main session model | Can override |
|
||||
| Output | Delivered if not `HEARTBEAT_OK` | Heartbeat prompt + event | Summary posted to main |
|
||||
|
||||
### When to use main session cron
|
||||
|
||||
Use `--session main` with `--system-event` when you want:
|
||||
- The reminder/event to appear in main session context
|
||||
- The agent to handle it during the next heartbeat with full context
|
||||
- No separate isolated run
|
||||
|
||||
```bash
|
||||
bot cron add \
|
||||
--name "Check project" \
|
||||
--every "4h" \
|
||||
--session main \
|
||||
--system-event "Time for a project health check" \
|
||||
--wake now
|
||||
```
|
||||
|
||||
### When to use isolated cron
|
||||
|
||||
Use `--session isolated` when you want:
|
||||
- A clean slate without prior context
|
||||
- Different model or thinking settings
|
||||
- Output delivered directly to a channel (summary still posts to main by default)
|
||||
- History that doesn't clutter main session
|
||||
|
||||
```bash
|
||||
bot cron add \
|
||||
--name "Deep analysis" \
|
||||
--cron "0 6 * * 0" \
|
||||
--session isolated \
|
||||
--message "Weekly codebase analysis..." \
|
||||
--model opus \
|
||||
--thinking high \
|
||||
--deliver
|
||||
```
|
||||
|
||||
## Cost Considerations
|
||||
|
||||
| Mechanism | Cost Profile |
|
||||
|-----------|--------------|
|
||||
| Heartbeat | One turn every N minutes; scales with HEARTBEAT.md size |
|
||||
| Cron (main) | Adds event to next heartbeat (no isolated turn) |
|
||||
| Cron (isolated) | Full agent turn per job; can use cheaper model |
|
||||
|
||||
**Tips**:
|
||||
- Keep `HEARTBEAT.md` small to minimize token overhead.
|
||||
- Batch similar checks into heartbeat instead of multiple cron jobs.
|
||||
- Use `target: "none"` on heartbeat if you only want internal processing.
|
||||
- Use isolated cron with a cheaper model for routine tasks.
|
||||
|
||||
## Related
|
||||
|
||||
- [Heartbeat](/gateway/heartbeat) - full heartbeat configuration
|
||||
- [Cron jobs](/automation/cron-jobs) - full cron CLI and API reference
|
||||
- [System](/cli/system) - system events + heartbeat controls
|
||||
@@ -0,0 +1,246 @@
|
||||
---
|
||||
title: "Gmail Pub/Sub -> Bot"
|
||||
description: "Gmail Pub/Sub push wired into Bot webhooks via gogcli"
|
||||
---
|
||||
|
||||
Goal: Gmail watch -> Pub/Sub push -> `gog gmail watch serve` -> Bot webhook.
|
||||
|
||||
## Prereqs
|
||||
|
||||
- `gcloud` installed and logged in ([install guide](https://docs.cloud.google.com/sdk/docs/install-sdk)).
|
||||
- `gog` (gogcli) installed and authorized for the Gmail account ([gogcli.sh](https://gogcli.sh/)).
|
||||
- Bot hooks enabled (see [Webhooks](/automation/webhook)).
|
||||
- `tailscale` logged in ([tailscale.com](https://tailscale.com/)). Supported setup uses Tailscale Funnel for the public HTTPS endpoint.
|
||||
Other tunnel services can work, but are DIY/unsupported and require manual wiring.
|
||||
Right now, Tailscale is what we support.
|
||||
|
||||
Example hook config (enable Gmail preset mapping):
|
||||
|
||||
```json5
|
||||
{
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "BOT_HOOK_TOKEN",
|
||||
path: "/hooks",
|
||||
presets: ["gmail"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To deliver the Gmail summary to a chat surface, override the preset with a mapping
|
||||
that sets `deliver` + optional `channel`/`to`:
|
||||
|
||||
```json5
|
||||
{
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "BOT_HOOK_TOKEN",
|
||||
presets: ["gmail"],
|
||||
mappings: [
|
||||
{
|
||||
match: { path: "gmail" },
|
||||
action: "agent",
|
||||
wakeMode: "now",
|
||||
name: "Gmail",
|
||||
sessionKey: "hook:gmail:{{messages[0].id}}",
|
||||
messageTemplate:
|
||||
"New email from {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}\n{{messages[0].body}}",
|
||||
model: "openai/gpt-5.2-mini",
|
||||
deliver: true,
|
||||
channel: "last"
|
||||
// to: "+15551234567"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you want a fixed channel, set `channel` + `to`. Otherwise `channel: "last"`
|
||||
uses the last delivery route (falls back to WhatsApp).
|
||||
|
||||
To force a cheaper model for Gmail runs, set `model` in the mapping
|
||||
(`provider/model` or alias). If you enforce `agents.defaults.models`, include it there.
|
||||
|
||||
To set a default model and thinking level specifically for Gmail hooks, add
|
||||
`hooks.gmail.model` / `hooks.gmail.thinking` in your config:
|
||||
|
||||
```json5
|
||||
{
|
||||
hooks: {
|
||||
gmail: {
|
||||
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
|
||||
thinking: "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Per-hook `model`/`thinking` in the mapping still overrides these defaults.
|
||||
- Fallback order: `hooks.gmail.model` → `agents.defaults.model.fallbacks` → primary (auth/rate-limit/timeouts).
|
||||
- If `agents.defaults.models` is set, the Gmail model must be in the allowlist.
|
||||
|
||||
To customize payload handling further, add `hooks.mappings` or a JS/TS transform module
|
||||
under `hooks.transformsDir` (see [Webhooks](/automation/webhook)).
|
||||
|
||||
## Wizard (recommended)
|
||||
|
||||
Use the Bot helper to wire everything together (installs deps on macOS via brew):
|
||||
|
||||
```bash
|
||||
bot webhooks gmail setup \
|
||||
--account [email protected]
|
||||
```
|
||||
|
||||
Defaults:
|
||||
- Uses Tailscale Funnel for the public push endpoint.
|
||||
- Writes `hooks.gmail` config for `bot webhooks gmail run`.
|
||||
- Enables the Gmail hook preset (`hooks.presets: ["gmail"]`).
|
||||
|
||||
Path note: when `tailscale.mode` is enabled, Bot automatically sets
|
||||
`hooks.gmail.serve.path` to `/` and keeps the public path at
|
||||
`hooks.gmail.tailscale.path` (default `/gmail-pubsub`) because Tailscale
|
||||
strips the set-path prefix before proxying.
|
||||
If you need the backend to receive the prefixed path, set
|
||||
`hooks.gmail.tailscale.target` (or `--tailscale-target`) to a full URL like
|
||||
`http://127.0.0.1:8788/gmail-pubsub` and match `hooks.gmail.serve.path`.
|
||||
|
||||
Want a custom endpoint? Use `--push-endpoint <url>` or `--tailscale off`.
|
||||
|
||||
Platform note: on macOS the wizard installs `gcloud`, `gogcli`, and `tailscale`
|
||||
via Homebrew; on Linux install them manually first.
|
||||
|
||||
Gateway auto-start (recommended):
|
||||
- When `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts
|
||||
`gog gmail watch serve` on boot and auto-renews the watch.
|
||||
- Set `BOT_SKIP_GMAIL_WATCHER=1` to opt out (useful if you run the daemon yourself).
|
||||
- Do not run the manual daemon at the same time, or you will hit
|
||||
`listen tcp 127.0.0.1:8788: bind: address already in use`.
|
||||
|
||||
Manual daemon (starts `gog gmail watch serve` + auto-renew):
|
||||
|
||||
```bash
|
||||
bot webhooks gmail run
|
||||
```
|
||||
|
||||
## One-time setup
|
||||
|
||||
1) Select the GCP project **that owns the OAuth client** used by `gog`.
|
||||
|
||||
```bash
|
||||
gcloud auth login
|
||||
gcloud config set project <project-id>
|
||||
```
|
||||
|
||||
Note: Gmail watch requires the Pub/Sub topic to live in the same project as the OAuth client.
|
||||
|
||||
2) Enable APIs:
|
||||
|
||||
```bash
|
||||
gcloud services enable gmail.googleapis.com pubsub.googleapis.com
|
||||
```
|
||||
|
||||
3) Create a topic:
|
||||
|
||||
```bash
|
||||
gcloud pubsub topics create gog-gmail-watch
|
||||
```
|
||||
|
||||
4) Allow Gmail push to publish:
|
||||
|
||||
```bash
|
||||
gcloud pubsub topics add-iam-policy-binding gog-gmail-watch \
|
||||
--member=serviceAccount:[email protected] \
|
||||
--role=roles/pubsub.publisher
|
||||
```
|
||||
|
||||
## Start the watch
|
||||
|
||||
```bash
|
||||
gog gmail watch start \
|
||||
--account [email protected] \
|
||||
--label INBOX \
|
||||
--topic projects/<project-id>/topics/gog-gmail-watch
|
||||
```
|
||||
|
||||
Save the `history_id` from the output (for debugging).
|
||||
|
||||
## Run the push handler
|
||||
|
||||
Local example (shared token auth):
|
||||
|
||||
```bash
|
||||
gog gmail watch serve \
|
||||
--account [email protected] \
|
||||
--bind 127.0.0.1 \
|
||||
--port 8788 \
|
||||
--path /gmail-pubsub \
|
||||
--token <shared> \
|
||||
--hook-url http://127.0.0.1:18789/hooks/gmail \
|
||||
--hook-token BOT_HOOK_TOKEN \
|
||||
--include-body \
|
||||
--max-bytes 20000
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `--token` protects the push endpoint (`x-gog-token` or `?token=`).
|
||||
- `--hook-url` points to Bot `/hooks/gmail` (mapped; isolated run + summary to main).
|
||||
- `--include-body` and `--max-bytes` control the body snippet sent to Bot.
|
||||
|
||||
Recommended: `bot webhooks gmail run` wraps the same flow and auto-renews the watch.
|
||||
|
||||
## Expose the handler (advanced, unsupported)
|
||||
|
||||
If you need a non-Tailscale tunnel, wire it manually and use the public URL in the push
|
||||
subscription (unsupported, no guardrails):
|
||||
|
||||
```bash
|
||||
cloudflared tunnel --url http://127.0.0.1:8788 --no-autoupdate
|
||||
```
|
||||
|
||||
Use the generated URL as the push endpoint:
|
||||
|
||||
```bash
|
||||
gcloud pubsub subscriptions create gog-gmail-watch-push \
|
||||
--topic gog-gmail-watch \
|
||||
--push-endpoint "https://<public-url>/gmail-pubsub?token=<shared>"
|
||||
```
|
||||
|
||||
Production: use a stable HTTPS endpoint and configure Pub/Sub OIDC JWT, then run:
|
||||
|
||||
```bash
|
||||
gog gmail watch serve --verify-oidc --oidc-email <svc@...>
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
Send a message to the watched inbox:
|
||||
|
||||
```bash
|
||||
gog gmail send \
|
||||
--account [email protected] \
|
||||
--to [email protected] \
|
||||
--subject "watch test" \
|
||||
--body "ping"
|
||||
```
|
||||
|
||||
Check watch state and history:
|
||||
|
||||
```bash
|
||||
gog gmail watch status --account [email protected]
|
||||
gog gmail history --account [email protected] --since <historyId>
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- `Invalid topicName`: project mismatch (topic not in the OAuth client project).
|
||||
- `User not authorized`: missing `roles/pubsub.publisher` on the topic.
|
||||
- Empty messages: Gmail push only provides `historyId`; fetch via `gog gmail history`.
|
||||
|
||||
## Cleanup
|
||||
|
||||
```bash
|
||||
gog gmail watch stop --account [email protected]
|
||||
gcloud pubsub subscriptions delete gog-gmail-watch-push
|
||||
gcloud pubsub topics delete gog-gmail-watch
|
||||
```
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"title": "Automation",
|
||||
"pages": [
|
||||
"auth-monitoring",
|
||||
"cron-jobs",
|
||||
"cron-vs-heartbeat",
|
||||
"gmail-pubsub",
|
||||
"poll",
|
||||
"webhook"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: "Polls"
|
||||
description: "Poll sending via gateway + CLI"
|
||||
---
|
||||
|
||||
## Supported channels
|
||||
- WhatsApp (web channel)
|
||||
- Discord
|
||||
- MS Teams (Adaptive Cards)
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
# WhatsApp
|
||||
bot message poll --target +15555550123 \
|
||||
--poll-question "Lunch today?" --poll-option "Yes" --poll-option "No" --poll-option "Maybe"
|
||||
bot message poll --target [email protected] \
|
||||
--poll-question "Meeting time?" --poll-option "10am" --poll-option "2pm" --poll-option "4pm" --poll-multi
|
||||
|
||||
# Discord
|
||||
bot message poll --channel discord --target channel:123456789 \
|
||||
--poll-question "Snack?" --poll-option "Pizza" --poll-option "Sushi"
|
||||
bot message poll --channel discord --target channel:123456789 \
|
||||
--poll-question "Plan?" --poll-option "A" --poll-option "B" --poll-duration-hours 48
|
||||
|
||||
# MS Teams
|
||||
bot message poll --channel msteams --target conversation:19:[email protected] \
|
||||
--poll-question "Lunch?" --poll-option "Pizza" --poll-option "Sushi"
|
||||
```
|
||||
|
||||
Options:
|
||||
- `--channel`: `whatsapp` (default), `discord`, or `msteams`
|
||||
- `--poll-multi`: allow selecting multiple options
|
||||
- `--poll-duration-hours`: Discord-only (defaults to 24 when omitted)
|
||||
|
||||
## Gateway RPC
|
||||
|
||||
Method: `poll`
|
||||
|
||||
Params:
|
||||
- `to` (string, required)
|
||||
- `question` (string, required)
|
||||
- `options` (string[], required)
|
||||
- `maxSelections` (number, optional)
|
||||
- `durationHours` (number, optional)
|
||||
- `channel` (string, optional, default: `whatsapp`)
|
||||
- `idempotencyKey` (string, required)
|
||||
|
||||
## Channel differences
|
||||
- WhatsApp: 2-12 options, `maxSelections` must be within option count, ignores `durationHours`.
|
||||
- Discord: 2-10 options, `durationHours` clamped to 1-768 hours (default 24). `maxSelections > 1` enables multi-select; Discord does not support a strict selection count.
|
||||
- MS Teams: Adaptive Card polls (Bot-managed). No native poll API; `durationHours` is ignored.
|
||||
|
||||
## Agent tool (Message)
|
||||
Use the `message` tool with `poll` action (`to`, `pollQuestion`, `pollOption`, optional `pollMulti`, `pollDurationHours`, `channel`).
|
||||
|
||||
Note: Discord has no “pick exactly N” mode; `pollMulti` maps to multi-select.
|
||||
Teams polls are rendered as Adaptive Cards and require the gateway to stay online
|
||||
to record votes in `~/.bot/msteams-polls.json`.
|
||||
@@ -0,0 +1,146 @@
|
||||
---
|
||||
title: "Webhooks"
|
||||
description: "Webhook ingress for wake and isolated agent runs"
|
||||
---
|
||||
|
||||
Gateway can expose a small HTTP webhook endpoint for external triggers.
|
||||
|
||||
## Enable
|
||||
|
||||
```json5
|
||||
{
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "shared-secret",
|
||||
path: "/hooks"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `hooks.token` is required when `hooks.enabled=true`.
|
||||
- `hooks.path` defaults to `/hooks`.
|
||||
|
||||
## Auth
|
||||
|
||||
Every request must include the hook token:
|
||||
- `Authorization: Bearer <token>`
|
||||
- or `x-bot-token: <token>`
|
||||
- or `?token=<token>`
|
||||
|
||||
## Endpoints
|
||||
|
||||
### `POST /hooks/wake`
|
||||
|
||||
Payload:
|
||||
```json
|
||||
{ "text": "System line", "mode": "now" }
|
||||
```
|
||||
|
||||
- `text` **required** (string): The description of the event (e.g., "New email received").
|
||||
- `mode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
|
||||
|
||||
Effect:
|
||||
- Enqueues a system event for the **main** session
|
||||
- If `mode=now`, triggers an immediate heartbeat
|
||||
|
||||
### `POST /hooks/agent`
|
||||
|
||||
Payload:
|
||||
```json
|
||||
{
|
||||
"message": "Run this",
|
||||
"name": "Email",
|
||||
"sessionKey": "hook:email:msg-123",
|
||||
"wakeMode": "now",
|
||||
"deliver": true,
|
||||
"channel": "last",
|
||||
"to": "+15551234567",
|
||||
"model": "openai/gpt-5.2-mini",
|
||||
"thinking": "low",
|
||||
"timeoutSeconds": 120
|
||||
}
|
||||
```
|
||||
|
||||
- `message` **required** (string): The prompt or message for the agent to process.
|
||||
- `name` optional (string): Human-readable name for the hook (e.g., "GitHub"), used as a prefix in session summaries.
|
||||
- `sessionKey` optional (string): The key used to identify the agent's session. Defaults to a random `hook:<uuid>`. Using a consistent key allows for a multi-turn conversation within the hook context.
|
||||
- `wakeMode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
|
||||
- `deliver` optional (boolean): If `true`, the agent's response will be sent to the messaging channel. Defaults to `true`. Responses that are only heartbeat acknowledgments are automatically skipped.
|
||||
- `channel` optional (string): The messaging channel for delivery. One of: `last`, `whatsapp`, `telegram`, `discord`, `slack`, `mattermost` (plugin), `signal`, `imessage`, `msteams`. Defaults to `last`.
|
||||
- `to` optional (string): The recipient identifier for the channel (e.g., phone number for WhatsApp/Signal, chat ID for Telegram, channel ID for Discord/Slack/Mattermost (plugin), conversation ID for MS Teams). Defaults to the last recipient in the main session.
|
||||
- `model` optional (string): Model override (e.g., `anthropic/claude-3-5-sonnet` or an alias). Must be in the allowed model list if restricted.
|
||||
- `thinking` optional (string): Thinking level override (e.g., `low`, `medium`, `high`).
|
||||
- `timeoutSeconds` optional (number): Maximum duration for the agent run in seconds.
|
||||
|
||||
Effect:
|
||||
- Runs an **isolated** agent turn (own session key)
|
||||
- Always posts a summary into the **main** session
|
||||
- If `wakeMode=now`, triggers an immediate heartbeat
|
||||
|
||||
### `POST /hooks/<name>` (mapped)
|
||||
|
||||
Custom hook names are resolved via `hooks.mappings` (see configuration). A mapping can
|
||||
turn arbitrary payloads into `wake` or `agent` actions, with optional templates or
|
||||
code transforms.
|
||||
|
||||
Mapping options (summary):
|
||||
- `hooks.presets: ["gmail"]` enables the built-in Gmail mapping.
|
||||
- `hooks.mappings` lets you define `match`, `action`, and templates in config.
|
||||
- `hooks.transformsDir` + `transform.module` loads a JS/TS module for custom logic.
|
||||
- Use `match.source` to keep a generic ingest endpoint (payload-driven routing).
|
||||
- TS transforms require a TS loader (e.g. `bun` or `tsx`) or precompiled `.js` at runtime.
|
||||
- Set `deliver: true` + `channel`/`to` on mappings to route replies to a chat surface
|
||||
(`channel` defaults to `last` and falls back to WhatsApp).
|
||||
- `bot webhooks gmail setup` writes `hooks.gmail` config for `bot webhooks gmail run`.
|
||||
See [Gmail Pub/Sub](/automation/gmail-pubsub) for the full Gmail watch flow.
|
||||
|
||||
## Responses
|
||||
|
||||
- `200` for `/hooks/wake`
|
||||
- `202` for `/hooks/agent` (async run started)
|
||||
- `401` on auth failure
|
||||
- `400` on invalid payload
|
||||
- `413` on oversized payloads
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:18789/hooks/wake \
|
||||
-H 'Authorization: Bearer SECRET' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"text":"New email received","mode":"now"}'
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:18789/hooks/agent \
|
||||
-H 'x-bot-token: SECRET' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"message":"Summarize inbox","name":"Email","wakeMode":"next-heartbeat"}'
|
||||
```
|
||||
|
||||
### Use a different model
|
||||
|
||||
Add `model` to the agent payload (or mapping) to override the model for that run:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:18789/hooks/agent \
|
||||
-H 'x-bot-token: SECRET' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"message":"Summarize inbox","name":"Email","model":"openai/gpt-5.2-mini"}'
|
||||
```
|
||||
|
||||
If you enforce `agents.defaults.models`, make sure the override model is included there.
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:18789/hooks/gmail \
|
||||
-H 'Authorization: Bearer SECRET' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"source":"gmail","messages":[{"from":"Ada","subject":"Hello","snippet":"Hi"}]}'
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
- Keep hook endpoints behind loopback, tailnet, or trusted reverse proxy.
|
||||
- Use a dedicated hook token; do not reuse gateway auth tokens.
|
||||
- Avoid including sensitive raw payloads in webhook logs.
|
||||
@@ -0,0 +1,228 @@
|
||||
---
|
||||
title: "BlueBubbles (macOS REST)"
|
||||
description: "iMessage via BlueBubbles macOS server (REST send/receive, typing, reactions, pairing, advanced actions)."
|
||||
---
|
||||
|
||||
Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **Recommended for iMessage integration** due to its richer API and easier setup compared to the legacy imsg channel.
|
||||
|
||||
## Overview
|
||||
- Runs on macOS via the BlueBubbles helper app ([bluebubbles.app](https://bluebubbles.app)).
|
||||
- Recommended/tested: macOS Sequoia (15). macOS Tahoe (26) works; edit is currently broken on Tahoe, and group icon updates may report success but not sync.
|
||||
- Bot talks to it through its REST API (`GET /api/v1/ping`, `POST /message/text`, `POST /chat/:id/*`).
|
||||
- Incoming messages arrive via webhooks; outgoing replies, typing indicators, read receipts, and tapbacks are REST calls.
|
||||
- Attachments and stickers are ingested as inbound media (and surfaced to the agent when possible).
|
||||
- Pairing/allowlist works the same way as other channels (`/start/pairing` etc) with `channels.bluebubbles.allowFrom` + pairing codes.
|
||||
- Reactions are surfaced as system events just like Slack/Telegram so agents can "mention" them before replying.
|
||||
- Advanced features: edit, unsend, reply threading, message effects, group management.
|
||||
|
||||
## Quick start
|
||||
1. Install the BlueBubbles server on your Mac (follow the instructions at [bluebubbles.app/install](https://bluebubbles.app/install)).
|
||||
2. In the BlueBubbles config, enable the web API and set a password.
|
||||
3. Run `bot onboard` and select BlueBubbles, or configure manually:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
enabled: true,
|
||||
serverUrl: "http://192.168.1.100:1234",
|
||||
password: "example-password",
|
||||
webhookPath: "/bluebubbles-webhook"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
4. Point BlueBubbles webhooks to your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=<password>`).
|
||||
5. Start the gateway; it will register the webhook handler and start pairing.
|
||||
|
||||
## Onboarding
|
||||
BlueBubbles is available in the interactive setup wizard:
|
||||
```
|
||||
bot onboard
|
||||
```
|
||||
|
||||
The wizard prompts for:
|
||||
- **Server URL** (required): BlueBubbles server address (e.g., `http://192.168.1.100:1234`)
|
||||
- **Password** (required): API password from BlueBubbles Server settings
|
||||
- **Webhook path** (optional): Defaults to `/bluebubbles-webhook`
|
||||
- **DM policy**: pairing, allowlist, open, or disabled
|
||||
- **Allow list**: Phone numbers, emails, or chat targets
|
||||
|
||||
You can also add BlueBubbles via CLI:
|
||||
```
|
||||
bot channels add bluebubbles --http-url http://192.168.1.100:1234 --password <password>
|
||||
```
|
||||
|
||||
## Access control (DMs + groups)
|
||||
DMs:
|
||||
- Default: `channels.bluebubbles.dmPolicy = "pairing"`.
|
||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
- `bot pairing list bluebubbles`
|
||||
- `bot pairing approve bluebubbles <CODE>`
|
||||
- Pairing is the default token exchange. Details: [Pairing](/start/pairing)
|
||||
|
||||
Groups:
|
||||
- `channels.bluebubbles.groupPolicy = open | allowlist | disabled` (default: `allowlist`).
|
||||
- `channels.bluebubbles.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||
|
||||
### Mention gating (groups)
|
||||
BlueBubbles supports mention gating for group chats, matching iMessage/WhatsApp behavior:
|
||||
- Uses `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) to detect mentions.
|
||||
- When `requireMention` is enabled for a group, the agent only responds when mentioned.
|
||||
- Control commands from authorized senders bypass mention gating.
|
||||
|
||||
Per-group configuration:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15555550123"],
|
||||
groups: {
|
||||
"*": { requireMention: true }, // default for all groups
|
||||
"iMessage;-;chat123": { requireMention: false } // override for specific group
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Command gating
|
||||
- Control commands (e.g., `/config`, `/model`) require authorization.
|
||||
- Uses `allowFrom` and `groupAllowFrom` to determine command authorization.
|
||||
- Authorized senders can run control commands even without mentioning in groups.
|
||||
|
||||
## Typing + read receipts
|
||||
- **Typing indicators**: Sent automatically before and during response generation.
|
||||
- **Read receipts**: Controlled by `channels.bluebubbles.sendReadReceipts` (default: `true`).
|
||||
- **Typing indicators**: Bot sends typing start events; BlueBubbles clears typing automatically on send or timeout (manual stop via DELETE is unreliable).
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
sendReadReceipts: false // disable read receipts
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced actions
|
||||
BlueBubbles supports advanced message actions when enabled in config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
actions: {
|
||||
reactions: true, // tapbacks (default: true)
|
||||
edit: true, // edit sent messages (macOS 13+, broken on macOS 26 Tahoe)
|
||||
unsend: true, // unsend messages (macOS 13+)
|
||||
reply: true, // reply threading by message GUID
|
||||
sendWithEffect: true, // message effects (slam, loud, etc.)
|
||||
renameGroup: true, // rename group chats
|
||||
setGroupIcon: true, // set group chat icon/photo (flaky on macOS 26 Tahoe)
|
||||
addParticipant: true, // add participants to groups
|
||||
removeParticipant: true, // remove participants from groups
|
||||
leaveGroup: true, // leave group chats
|
||||
sendAttachment: true // send attachments/media
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Available actions:
|
||||
- **react**: Add/remove tapback reactions (`messageId`, `emoji`, `remove`)
|
||||
- **edit**: Edit a sent message (`messageId`, `text`)
|
||||
- **unsend**: Unsend a message (`messageId`)
|
||||
- **reply**: Reply to a specific message (`messageId`, `text`, `to`)
|
||||
- **sendWithEffect**: Send with iMessage effect (`text`, `to`, `effectId`)
|
||||
- **renameGroup**: Rename a group chat (`chatGuid`, `displayName`)
|
||||
- **setGroupIcon**: Set a group chat's icon/photo (`chatGuid`, `media`) — flaky on macOS 26 Tahoe (API may return success but the icon does not sync).
|
||||
- **addParticipant**: Add someone to a group (`chatGuid`, `address`)
|
||||
- **removeParticipant**: Remove someone from a group (`chatGuid`, `address`)
|
||||
- **leaveGroup**: Leave a group chat (`chatGuid`)
|
||||
- **sendAttachment**: Send media/files (`to`, `buffer`, `filename`, `asVoice`)
|
||||
- Voice memos: set `asVoice: true` with **MP3** or **CAF** audio to send as an iMessage voice message. BlueBubbles converts MP3 → CAF when sending voice memos.
|
||||
|
||||
### Message IDs (short vs full)
|
||||
Bot may surface *short* message IDs (e.g., `1`, `2`) to save tokens.
|
||||
- `MessageSid` / `ReplyToId` can be short IDs.
|
||||
- `MessageSidFull` / `ReplyToIdFull` contain the provider full IDs.
|
||||
- Short IDs are in-memory; they can expire on restart or cache eviction.
|
||||
- Actions accept short or full `messageId`, but short IDs will error if no longer available.
|
||||
|
||||
Use full IDs for durable automations and storage:
|
||||
- Templates: `{{MessageSidFull}}`, `{{ReplyToIdFull}}`
|
||||
- Context: `MessageSidFull` / `ReplyToIdFull` in inbound payloads
|
||||
|
||||
See [Configuration](/gateway/configuration) for template variables.
|
||||
|
||||
## Block streaming
|
||||
Control whether responses are sent as a single message or streamed in blocks:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
blockStreaming: true // enable block streaming (default behavior)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Media + limits
|
||||
- Inbound attachments are downloaded and stored in the media cache.
|
||||
- Media cap via `channels.bluebubbles.mediaMaxMb` (default: 8 MB).
|
||||
- Outbound text is chunked to `channels.bluebubbles.textChunkLimit` (default: 4000 chars).
|
||||
|
||||
## Configuration reference
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
- `channels.bluebubbles.enabled`: Enable/disable the channel.
|
||||
- `channels.bluebubbles.serverUrl`: BlueBubbles REST API base URL.
|
||||
- `channels.bluebubbles.password`: API password.
|
||||
- `channels.bluebubbles.webhookPath`: Webhook endpoint path (default: `/bluebubbles-webhook`).
|
||||
- `channels.bluebubbles.dmPolicy`: `pairing | allowlist | open | disabled` (default: `pairing`).
|
||||
- `channels.bluebubbles.allowFrom`: DM allowlist (handles, emails, E.164 numbers, `chat_id:*`, `chat_guid:*`).
|
||||
- `channels.bluebubbles.groupPolicy`: `open | allowlist | disabled` (default: `allowlist`).
|
||||
- `channels.bluebubbles.groupAllowFrom`: Group sender allowlist.
|
||||
- `channels.bluebubbles.groups`: Per-group config (`requireMention`, etc.).
|
||||
- `channels.bluebubbles.sendReadReceipts`: Send read receipts (default: `true`).
|
||||
- `channels.bluebubbles.blockStreaming`: Enable block streaming (default: `true`).
|
||||
- `channels.bluebubbles.textChunkLimit`: Outbound chunk size in chars (default: 4000).
|
||||
- `channels.bluebubbles.chunkMode`: `length` (default) splits only when exceeding `textChunkLimit`; `newline` splits on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.bluebubbles.mediaMaxMb`: Inbound media cap in MB (default: 8).
|
||||
- `channels.bluebubbles.historyLimit`: Max group messages for context (0 disables).
|
||||
- `channels.bluebubbles.dmHistoryLimit`: DM history limit.
|
||||
- `channels.bluebubbles.actions`: Enable/disable specific actions.
|
||||
- `channels.bluebubbles.accounts`: Multi-account configuration.
|
||||
|
||||
Related global options:
|
||||
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`).
|
||||
- `messages.responsePrefix`.
|
||||
|
||||
## Addressing / delivery targets
|
||||
Prefer `chat_guid` for stable routing:
|
||||
- `chat_guid:iMessage;-;+15555550123` (preferred for groups)
|
||||
- `chat_id:123`
|
||||
- `chat_identifier:...`
|
||||
- Direct handles: `+15555550123`, `[email protected]`
|
||||
- If a direct handle does not have an existing DM chat, Bot will create one via `POST /api/v1/chat/new`. This requires the BlueBubbles Private API to be enabled.
|
||||
|
||||
## Security
|
||||
- Webhook requests are authenticated by comparing `guid`/`password` query params or headers against `channels.bluebubbles.password`. Requests from `localhost` are also accepted.
|
||||
- Keep the API password and webhook endpoint secret (treat them like credentials).
|
||||
- Enable HTTPS + firewall rules on the BlueBubbles server if exposing it outside your LAN.
|
||||
|
||||
## Troubleshooting
|
||||
- If typing/read events stop working, check the BlueBubbles webhook logs and verify the gateway path matches `channels.bluebubbles.webhookPath`.
|
||||
- Pairing codes expire after one hour; use `bot pairing list bluebubbles` and `bot pairing approve bluebubbles <code>`.
|
||||
- Reactions require the BlueBubbles private API (`POST /api/v1/message/react`); ensure the server version exposes it.
|
||||
- Edit/unsend require macOS 13+ and a compatible BlueBubbles server version. On macOS 26 (Tahoe), edit is currently broken due to private API changes.
|
||||
- Group icon updates can be flaky on macOS 26 (Tahoe): the API may return success but the new icon does not sync.
|
||||
- Bot auto-hides known-broken actions based on the BlueBubbles server's macOS version. If edit still appears on macOS 26 (Tahoe), disable it manually with `channels.bluebubbles.actions.edit=false`.
|
||||
- For status/health info: `bot status --all` or `bot status --deep`.
|
||||
|
||||
For general channel workflow reference, see [Channels](/channels) and the [Plugins](/plugins) guide.
|
||||
@@ -0,0 +1,396 @@
|
||||
---
|
||||
title: "Discord (Bot API)"
|
||||
description: "Discord bot support status, capabilities, and configuration"
|
||||
---
|
||||
|
||||
Status: ready for DM and guild text channels via the official Discord bot gateway.
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Create a Discord bot and copy the bot token.
|
||||
2) Set the token for Bot:
|
||||
- Env: `DISCORD_BOT_TOKEN=...`
|
||||
- Or config: `channels.discord.token: "..."`.
|
||||
- If both are set, config takes precedence (env fallback is default-account only).
|
||||
3) Invite the bot to your server with message permissions.
|
||||
4) Start the gateway.
|
||||
5) DM access is pairing by default; approve the pairing code on first contact.
|
||||
|
||||
Minimal config:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
discord: {
|
||||
enabled: true,
|
||||
token: "YOUR_BOT_TOKEN"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Goals
|
||||
- Talk to Bot via Discord DMs or guild channels.
|
||||
- Direct chats collapse into the agent's main session (default `agent:main:main`); guild channels stay isolated as `agent:<agentId>:discord:channel:<channelId>` (display names use `discord:<guildSlug>#<channelSlug>`).
|
||||
- Group DMs are ignored by default; enable via `channels.discord.dm.groupEnabled` and optionally restrict by `channels.discord.dm.groupChannels`.
|
||||
- Keep routing deterministic: replies always go back to the channel they arrived on.
|
||||
|
||||
## How it works
|
||||
1. Create a Discord application → Bot, enable the intents you need (DMs + guild messages + message content), and grab the bot token.
|
||||
2. Invite the bot to your server with the permissions required to read/send messages where you want to use it.
|
||||
3. Configure Bot with `channels.discord.token` (or `DISCORD_BOT_TOKEN` as a fallback).
|
||||
4. Run the gateway; it auto-starts the Discord channel when a token is available (config first, env fallback) and `channels.discord.enabled` is not `false`.
|
||||
- If you prefer env vars, set `DISCORD_BOT_TOKEN` (a config block is optional).
|
||||
5. Direct chats: use `user:<id>` (or a `<@id>` mention) when delivering; all turns land in the shared `main` session. Bare numeric IDs are ambiguous and rejected.
|
||||
6. Guild channels: use `channel:<channelId>` for delivery. Mentions are required by default and can be set per guild or per channel.
|
||||
7. Direct chats: secure by default via `channels.discord.dm.policy` (default: `"pairing"`). Unknown senders get a pairing code (expires after 1 hour); approve via `bot pairing approve discord <code>`.
|
||||
- To keep old “open to anyone” behavior: set `channels.discord.dm.policy="open"` and `channels.discord.dm.allowFrom=["*"]`.
|
||||
- To hard-allowlist: set `channels.discord.dm.policy="allowlist"` and list senders in `channels.discord.dm.allowFrom`.
|
||||
- To ignore all DMs: set `channels.discord.dm.enabled=false` or `channels.discord.dm.policy="disabled"`.
|
||||
8. Group DMs are ignored by default; enable via `channels.discord.dm.groupEnabled` and optionally restrict by `channels.discord.dm.groupChannels`.
|
||||
9. Optional guild rules: set `channels.discord.guilds` keyed by guild id (preferred) or slug, with per-channel rules.
|
||||
10. Optional native commands: `commands.native` defaults to `"auto"` (on for Discord/Telegram, off for Slack). Override with `channels.discord.commands.native: true|false|"auto"`; `false` clears previously registered commands. Text commands are controlled by `commands.text` and must be sent as standalone `/...` messages. Use `commands.useAccessGroups: false` to bypass access-group checks for commands.
|
||||
- Full command list + config: [Slash commands](/tools/slash-commands)
|
||||
11. Optional guild context history: set `channels.discord.historyLimit` (default 20, falls back to `messages.groupChat.historyLimit`) to include the last N guild messages as context when replying to a mention. Set `0` to disable.
|
||||
12. Reactions: the agent can trigger reactions via the `discord` tool (gated by `channels.discord.actions.*`).
|
||||
- Reaction removal semantics: see [/tools/reactions](/tools/reactions).
|
||||
- The `discord` tool is only exposed when the current channel is Discord.
|
||||
13. Native commands use isolated session keys (`agent:<agentId>:discord:slash:<userId>`) rather than the shared `main` session.
|
||||
|
||||
Note: Name → id resolution uses guild member search and requires Server Members Intent; if the bot can’t search members, use ids or `<@id>` mentions.
|
||||
Note: Slugs are lowercase with spaces replaced by `-`. Channel names are slugged without the leading `#`.
|
||||
Note: Guild context `[from:]` lines include `author.tag` + `id` to make ping-ready replies easy.
|
||||
|
||||
## Config writes
|
||||
By default, Discord is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||
|
||||
Disable with:
|
||||
```json5
|
||||
{
|
||||
channels: { discord: { configWrites: false } }
|
||||
}
|
||||
```
|
||||
|
||||
## How to create your own bot
|
||||
|
||||
This is the “Discord Developer Portal” setup for running Bot in a server (guild) channel like `#help`.
|
||||
|
||||
### 1) Create the Discord app + bot user
|
||||
1. Discord Developer Portal → **Applications** → **New Application**
|
||||
2. In your app:
|
||||
- **Bot** → **Add Bot**
|
||||
- Copy the **Bot Token** (this is what you put in `DISCORD_BOT_TOKEN`)
|
||||
|
||||
### 2) Enable the gateway intents Bot needs
|
||||
Discord blocks “privileged intents” unless you explicitly enable them.
|
||||
|
||||
In **Bot** → **Privileged Gateway Intents**, enable:
|
||||
- **Message Content Intent** (required to read message text in most guilds; without it you’ll see “Used disallowed intents” or the bot will connect but not react to messages)
|
||||
- **Server Members Intent** (recommended; required for some member/user lookups and allowlist matching in guilds)
|
||||
|
||||
You usually do **not** need **Presence Intent**.
|
||||
|
||||
### 3) Generate an invite URL (OAuth2 URL Generator)
|
||||
In your app: **OAuth2** → **URL Generator**
|
||||
|
||||
**Scopes**
|
||||
- ✅ `bot`
|
||||
- ✅ `applications.commands` (required for native commands)
|
||||
|
||||
**Bot Permissions** (minimal baseline)
|
||||
- ✅ View Channels
|
||||
- ✅ Send Messages
|
||||
- ✅ Read Message History
|
||||
- ✅ Embed Links
|
||||
- ✅ Attach Files
|
||||
- ✅ Add Reactions (optional but recommended)
|
||||
- ✅ Use External Emojis / Stickers (optional; only if you want them)
|
||||
|
||||
Avoid **Administrator** unless you’re debugging and fully trust the bot.
|
||||
|
||||
Copy the generated URL, open it, pick your server, and install the bot.
|
||||
|
||||
### 4) Get the ids (guild/user/channel)
|
||||
Discord uses numeric ids everywhere; Bot config prefers ids.
|
||||
|
||||
1. Discord (desktop/web) → **User Settings** → **Advanced** → enable **Developer Mode**
|
||||
2. Right-click:
|
||||
- Server name → **Copy Server ID** (guild id)
|
||||
- Channel (e.g. `#help`) → **Copy Channel ID**
|
||||
- Your user → **Copy User ID**
|
||||
|
||||
### 5) Configure Bot
|
||||
|
||||
#### Token
|
||||
Set the bot token via env var (recommended on servers):
|
||||
- `DISCORD_BOT_TOKEN=...`
|
||||
|
||||
Or via config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
discord: {
|
||||
enabled: true,
|
||||
token: "YOUR_BOT_TOKEN"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Multi-account support: use `channels.discord.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
||||
|
||||
#### Allowlist + channel routing
|
||||
Example “single server, only allow me, only allow #help”:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
discord: {
|
||||
enabled: true,
|
||||
dm: { enabled: false },
|
||||
guilds: {
|
||||
"YOUR_GUILD_ID": {
|
||||
users: ["YOUR_USER_ID"],
|
||||
requireMention: true,
|
||||
channels: {
|
||||
help: { allow: true, requireMention: true }
|
||||
}
|
||||
}
|
||||
},
|
||||
retry: {
|
||||
attempts: 3,
|
||||
minDelayMs: 500,
|
||||
maxDelayMs: 30000,
|
||||
jitter: 0.1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `requireMention: true` means the bot only replies when mentioned (recommended for shared channels).
|
||||
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions for guild messages.
|
||||
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
||||
- If `channels` is present, any channel not listed is denied by default.
|
||||
- Use a `"*"` channel entry to apply defaults across all channels; explicit channel entries override the wildcard.
|
||||
- Threads inherit parent channel config (allowlist, `requireMention`, skills, prompts, etc.) unless you add the thread channel id explicitly.
|
||||
- Bot-authored messages are ignored by default; set `channels.discord.allowBots=true` to allow them (own messages remain filtered).
|
||||
- Warning: If you allow replies to other bots (`channels.discord.allowBots=true`), prevent bot-to-bot reply loops with `requireMention`, `channels.discord.guilds.*.channels.<id>.users` allowlists, and/or clear guardrails in `AGENTS.md` and `SOUL.md`.
|
||||
|
||||
### 6) Verify it works
|
||||
1. Start the gateway.
|
||||
2. In your server channel, send: `@Krill hello` (or whatever your bot name is).
|
||||
3. If nothing happens: check **Troubleshooting** below.
|
||||
|
||||
### Troubleshooting
|
||||
- First: run `bot doctor` and `bot channels status --probe` (actionable warnings + quick audits).
|
||||
- **“Used disallowed intents”**: enable **Message Content Intent** (and likely **Server Members Intent**) in the Developer Portal, then restart the gateway.
|
||||
- **Bot connects but never replies in a guild channel**:
|
||||
- Missing **Message Content Intent**, or
|
||||
- The bot lacks channel permissions (View/Send/Read History), or
|
||||
- Your config requires mentions and you didn’t mention it, or
|
||||
- Your guild/channel allowlist denies the channel/user.
|
||||
- **`requireMention: false` but still no replies**:
|
||||
- `channels.discord.groupPolicy` defaults to **allowlist**; set it to `"open"` or add a guild entry under `channels.discord.guilds` (optionally list channels under `channels.discord.guilds.<id>.channels` to restrict).
|
||||
- If you only set `DISCORD_BOT_TOKEN` and never create a `channels.discord` section, the runtime
|
||||
defaults `groupPolicy` to `open`. Add `channels.discord.groupPolicy`,
|
||||
`channels.defaults.groupPolicy`, or a guild/channel allowlist to lock it down.
|
||||
- `requireMention` must live under `channels.discord.guilds` (or a specific channel). `channels.discord.requireMention` at the top level is ignored.
|
||||
- **Permission audits** (`channels status --probe`) only check numeric channel IDs. If you use slugs/names as `channels.discord.guilds.*.channels` keys, the audit can’t verify permissions.
|
||||
- **DMs don’t work**: `channels.discord.dm.enabled=false`, `channels.discord.dm.policy="disabled"`, or you haven’t been approved yet (`channels.discord.dm.policy="pairing"`).
|
||||
|
||||
## Capabilities & limits
|
||||
- DMs and guild text channels (threads are treated as separate channels; voice not supported).
|
||||
- Typing indicators sent best-effort; message chunking uses `channels.discord.textChunkLimit` (default 2000) and splits tall replies by line count (`channels.discord.maxLinesPerMessage`, default 17).
|
||||
- Optional newline chunking: set `channels.discord.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- File uploads supported up to the configured `channels.discord.mediaMaxMb` (default 8 MB).
|
||||
- Mention-gated guild replies by default to avoid noisy bots.
|
||||
- Reply context is injected when a message references another message (quoted content + ids).
|
||||
- Native reply threading is **off by default**; enable with `channels.discord.replyToMode` and reply tags.
|
||||
|
||||
## Retry policy
|
||||
Outbound Discord API calls retry on rate limits (429) using Discord `retry_after` when available, with exponential backoff and jitter. Configure via `channels.discord.retry`. See [Retry policy](/concepts/retry).
|
||||
|
||||
## Config
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
discord: {
|
||||
enabled: true,
|
||||
token: "abc.123",
|
||||
groupPolicy: "allowlist",
|
||||
guilds: {
|
||||
"*": {
|
||||
channels: {
|
||||
general: { allow: true }
|
||||
}
|
||||
}
|
||||
},
|
||||
mediaMaxMb: 8,
|
||||
actions: {
|
||||
reactions: true,
|
||||
stickers: true,
|
||||
emojiUploads: true,
|
||||
stickerUploads: true,
|
||||
polls: true,
|
||||
permissions: true,
|
||||
messages: true,
|
||||
threads: true,
|
||||
pins: true,
|
||||
search: true,
|
||||
memberInfo: true,
|
||||
roleInfo: true,
|
||||
roles: false,
|
||||
channelInfo: true,
|
||||
channels: true,
|
||||
voiceStatus: true,
|
||||
events: true,
|
||||
moderation: false
|
||||
},
|
||||
replyToMode: "off",
|
||||
dm: {
|
||||
enabled: true,
|
||||
policy: "pairing", // pairing | allowlist | open | disabled
|
||||
allowFrom: ["123456789012345678", "steipete"],
|
||||
groupEnabled: false,
|
||||
groupChannels: ["bot-dm"]
|
||||
},
|
||||
guilds: {
|
||||
"*": { requireMention: true },
|
||||
"123456789012345678": {
|
||||
slug: "friends-of-bot",
|
||||
requireMention: false,
|
||||
reactionNotifications: "own",
|
||||
users: ["987654321098765432", "steipete"],
|
||||
channels: {
|
||||
general: { allow: true },
|
||||
help: {
|
||||
allow: true,
|
||||
requireMention: true,
|
||||
users: ["987654321098765432"],
|
||||
skills: ["search", "docs"],
|
||||
systemPrompt: "Keep answers short."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ack reactions are controlled globally via `messages.ackReaction` +
|
||||
`messages.ackReactionScope`. Use `messages.removeAckAfterReply` to clear the
|
||||
ack reaction after the bot replies.
|
||||
|
||||
- `dm.enabled`: set `false` to ignore all DMs (default `true`).
|
||||
- `dm.policy`: DM access control (`pairing` recommended). `"open"` requires `dm.allowFrom=["*"]`.
|
||||
- `dm.allowFrom`: DM allowlist (user ids or names). Used by `dm.policy="allowlist"` and for `dm.policy="open"` validation. The wizard accepts usernames and resolves them to ids when the bot can search members.
|
||||
- `dm.groupEnabled`: enable group DMs (default `false`).
|
||||
- `dm.groupChannels`: optional allowlist for group DM channel ids or slugs.
|
||||
- `groupPolicy`: controls guild channel handling (`open|disabled|allowlist`); `allowlist` requires channel allowlists.
|
||||
- `guilds`: per-guild rules keyed by guild id (preferred) or slug.
|
||||
- `guilds."*"`: default per-guild settings applied when no explicit entry exists.
|
||||
- `guilds.<id>.slug`: optional friendly slug used for display names.
|
||||
- `guilds.<id>.users`: optional per-guild user allowlist (ids or names).
|
||||
- `guilds.<id>.channels.<channel>.allow`: allow/deny the channel when `groupPolicy="allowlist"`.
|
||||
- `guilds.<id>.channels.<channel>.requireMention`: mention gating for the channel.
|
||||
- `guilds.<id>.channels.<channel>.users`: optional per-channel user allowlist.
|
||||
- `guilds.<id>.channels.<channel>.skills`: skill filter (omit = all skills, empty = none).
|
||||
- `guilds.<id>.channels.<channel>.systemPrompt`: extra system prompt for the channel (combined with channel topic).
|
||||
- `guilds.<id>.channels.<channel>.enabled`: set `false` to disable the channel.
|
||||
- `guilds.<id>.channels`: channel rules (keys are channel slugs or ids).
|
||||
- `guilds.<id>.requireMention`: per-guild mention requirement (overridable per channel).
|
||||
- `guilds.<id>.reactionNotifications`: reaction system event mode (`off`, `own`, `all`, `allowlist`).
|
||||
- `textChunkLimit`: outbound text chunk size (chars). Default: 2000.
|
||||
- `chunkMode`: `length` (default) splits only when exceeding `textChunkLimit`; `newline` splits on blank lines (paragraph boundaries) before length chunking.
|
||||
- `maxLinesPerMessage`: soft max line count per message. Default: 17.
|
||||
- `mediaMaxMb`: clamp inbound media saved to disk.
|
||||
- `historyLimit`: number of recent guild messages to include as context when replying to a mention (default 20; falls back to `messages.groupChat.historyLimit`; `0` disables).
|
||||
- `dmHistoryLimit`: DM history limit in user turns. Per-user overrides: `dms["<user_id>"].historyLimit`.
|
||||
- `retry`: retry policy for outbound Discord API calls (attempts, minDelayMs, maxDelayMs, jitter).
|
||||
- `actions`: per-action tool gates; omit to allow all (set `false` to disable).
|
||||
- `reactions` (covers react + read reactions)
|
||||
- `stickers`, `emojiUploads`, `stickerUploads`, `polls`, `permissions`, `messages`, `threads`, `pins`, `search`
|
||||
- `memberInfo`, `roleInfo`, `channelInfo`, `voiceStatus`, `events`
|
||||
- `channels` (create/edit/delete channels + categories + permissions)
|
||||
- `roles` (role add/remove, default `false`)
|
||||
- `moderation` (timeout/kick/ban, default `false`)
|
||||
|
||||
Reaction notifications use `guilds.<id>.reactionNotifications`:
|
||||
- `off`: no reaction events.
|
||||
- `own`: reactions on the bot's own messages (default).
|
||||
- `all`: all reactions on all messages.
|
||||
- `allowlist`: reactions from `guilds.<id>.users` on all messages (empty list disables).
|
||||
|
||||
### Tool action defaults
|
||||
|
||||
| Action group | Default | Notes |
|
||||
| --- | --- | --- |
|
||||
| reactions | enabled | React + list reactions + emojiList |
|
||||
| stickers | enabled | Send stickers |
|
||||
| emojiUploads | enabled | Upload emojis |
|
||||
| stickerUploads | enabled | Upload stickers |
|
||||
| polls | enabled | Create polls |
|
||||
| permissions | enabled | Channel permission snapshot |
|
||||
| messages | enabled | Read/send/edit/delete |
|
||||
| threads | enabled | Create/list/reply |
|
||||
| pins | enabled | Pin/unpin/list |
|
||||
| search | enabled | Message search (preview feature) |
|
||||
| memberInfo | enabled | Member info |
|
||||
| roleInfo | enabled | Role list |
|
||||
| channelInfo | enabled | Channel info + list |
|
||||
| channels | enabled | Channel/category management |
|
||||
| voiceStatus | enabled | Voice state lookup |
|
||||
| events | enabled | List/create scheduled events |
|
||||
| roles | disabled | Role add/remove |
|
||||
| moderation | disabled | Timeout/kick/ban |
|
||||
- `replyToMode`: `off` (default), `first`, or `all`. Applies only when the model includes a reply tag.
|
||||
|
||||
## Reply tags
|
||||
To request a threaded reply, the model can include one tag in its output:
|
||||
- `[[reply_to_current]]` — reply to the triggering Discord message.
|
||||
- `[[reply_to:<id>]]` — reply to a specific message id from context/history.
|
||||
Current message ids are appended to prompts as `[message_id: …]`; history entries already include ids.
|
||||
|
||||
Behavior is controlled by `channels.discord.replyToMode`:
|
||||
- `off`: ignore tags.
|
||||
- `first`: only the first outbound chunk/attachment is a reply.
|
||||
- `all`: every outbound chunk/attachment is a reply.
|
||||
|
||||
Allowlist matching notes:
|
||||
- `allowFrom`/`users`/`groupChannels` accept ids, names, tags, or mentions like `<@id>`.
|
||||
- Prefixes like `discord:`/`user:` (users) and `channel:` (group DMs) are supported.
|
||||
- Use `*` to allow any sender/channel.
|
||||
- When `guilds.<id>.channels` is present, channels not listed are denied by default.
|
||||
- When `guilds.<id>.channels` is omitted, all channels in the allowlisted guild are allowed.
|
||||
- To allow **no channels**, set `channels.discord.groupPolicy: "disabled"` (or keep an empty allowlist).
|
||||
- The configure wizard accepts `Guild/Channel` names (public + private) and resolves them to IDs when possible.
|
||||
- On startup, Bot resolves channel/user names in allowlists to IDs (when the bot can search members)
|
||||
and logs the mapping; unresolved entries are kept as typed.
|
||||
|
||||
Native command notes:
|
||||
- The registered commands mirror Bot’s chat commands.
|
||||
- Native commands honor the same allowlists as DMs/guild messages (`channels.discord.dm.allowFrom`, `channels.discord.guilds`, per-channel rules).
|
||||
- Slash commands may still be visible in Discord UI to users who aren’t allowlisted; Bot enforces allowlists on execution and replies “not authorized”.
|
||||
|
||||
## Tool actions
|
||||
The agent can call `discord` with actions like:
|
||||
- `react` / `reactions` (add or list reactions)
|
||||
- `sticker`, `poll`, `permissions`
|
||||
- `readMessages`, `sendMessage`, `editMessage`, `deleteMessage`
|
||||
- Read/search/pin tool payloads include normalized `timestampMs` (UTC epoch ms) and `timestampUtc` alongside raw Discord `timestamp`.
|
||||
- `threadCreate`, `threadList`, `threadReply`
|
||||
- `pinMessage`, `unpinMessage`, `listPins`
|
||||
- `searchMessages`, `memberInfo`, `roleInfo`, `roleAdd`, `roleRemove`, `emojiList`
|
||||
- `channelInfo`, `channelList`, `voiceStatus`, `eventList`, `eventCreate`
|
||||
- `timeout`, `kick`, `ban`
|
||||
|
||||
Discord message ids are surfaced in the injected context (`[discord message id: …]` and history lines) so the agent can target them.
|
||||
Emoji can be unicode (e.g., `✅`) or custom emoji syntax like `<:party_blob:1234567890>`.
|
||||
|
||||
## Safety & ops
|
||||
- Treat the bot token like a password; prefer the `DISCORD_BOT_TOKEN` env var on supervised hosts or lock down the config file permissions.
|
||||
- Only grant the bot permissions it needs (typically Read/Send Messages).
|
||||
- If the bot is stuck or rate limited, restart the gateway (`bot gateway --force`) after confirming no other processes own the Discord session.
|
||||
@@ -0,0 +1,218 @@
|
||||
---
|
||||
title: "Google Chat (Chat API)"
|
||||
description: "Google Chat app support status, capabilities, and configuration"
|
||||
---
|
||||
|
||||
Status: ready for DMs + spaces via Google Chat API webhooks (HTTP only).
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Create a Google Cloud project and enable the **Google Chat API**.
|
||||
- Go to: [Google Chat API Credentials](https://console.cloud.google.com/apis/api/chat.googleapis.com/credentials)
|
||||
- Enable the API if it is not already enabled.
|
||||
2) Create a **Service Account**:
|
||||
- Press **Create Credentials** > **Service Account**.
|
||||
- Name it whatever you want (e.g., `bot-chat`).
|
||||
- Leave permissions blank (press **Continue**).
|
||||
- Leave principals with access blank (press **Done**).
|
||||
3) Create and download the **JSON Key**:
|
||||
- In the list of service accounts, click on the one you just created.
|
||||
- Go to the **Keys** tab.
|
||||
- Click **Add Key** > **Create new key**.
|
||||
- Select **JSON** and press **Create**.
|
||||
4) Store the downloaded JSON file on your gateway host (e.g., `~/.bot/googlechat-service-account.json`).
|
||||
5) Create a Google Chat app in the [Google Cloud Console Chat Configuration](https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat):
|
||||
- Fill in the **Application info**:
|
||||
- **App name**: (e.g. `Bot`)
|
||||
- **Avatar URL**: (e.g. `https://hanzo.bot/logo.png`)
|
||||
- **Description**: (e.g. `Personal AI Assistant`)
|
||||
- Enable **Interactive features**.
|
||||
- Under **Functionality**, check **Join spaces and group conversations**.
|
||||
- Under **Connection settings**, select **HTTP endpoint URL**.
|
||||
- Under **Triggers**, select **Use a common HTTP endpoint URL for all triggers** and set it to your gateway's public URL followed by `/googlechat`.
|
||||
- *Tip: Run `bot status` to find your gateway's public URL.*
|
||||
- Under **Visibility**, check **Make this Chat app available to specific people and groups in <Your Domain>**.
|
||||
- Enter your email address (e.g. `[email protected]`) in the text box.
|
||||
- Click **Save** at the bottom.
|
||||
6) **Enable the app status**:
|
||||
- After saving, **refresh the page**.
|
||||
- Look for the **App status** section (usually near the top or bottom after saving).
|
||||
- Change the status to **Live - available to users**.
|
||||
- Click **Save** again.
|
||||
7) Configure Bot with the service account path + webhook audience:
|
||||
- Env: `GOOGLE_CHAT_SERVICE_ACCOUNT_FILE=/path/to/service-account.json`
|
||||
- Or config: `channels.googlechat.serviceAccountFile: "/path/to/service-account.json"`.
|
||||
8) Set the webhook audience type + value (matches your Chat app config).
|
||||
9) Start the gateway. Google Chat will POST to your webhook path.
|
||||
|
||||
## Add to Google Chat
|
||||
Once the gateway is running and your email is added to the visibility list:
|
||||
1) Go to [Google Chat](https://chat.google.com/).
|
||||
2) Click the **+** (plus) icon next to **Direct Messages**.
|
||||
3) In the search bar (where you usually add people), type the **App name** you configured in the Google Cloud Console.
|
||||
- **Note**: The bot will *not* appear in the "Marketplace" browse list because it is a private app. You must search for it by name.
|
||||
4) Select your bot from the results.
|
||||
5) Click **Add** or **Chat** to start a 1:1 conversation.
|
||||
6) Send "Hello" to trigger the assistant!
|
||||
|
||||
## Public URL (Webhook-only)
|
||||
Google Chat webhooks require a public HTTPS endpoint. For security, **only expose the `/googlechat` path** to the internet. Keep the Bot dashboard and other sensitive endpoints on your private network.
|
||||
|
||||
### Option A: Tailscale Funnel (Recommended)
|
||||
Use Tailscale Serve for the private dashboard and Funnel for the public webhook path. This keeps `/` private while exposing only `/googlechat`.
|
||||
|
||||
1. **Check what address your gateway is bound to:**
|
||||
```bash
|
||||
ss -tlnp | grep 18789
|
||||
```
|
||||
Note the IP address (e.g., `127.0.0.1`, `0.0.0.0`, or your Tailscale IP like `100.x.x.x`).
|
||||
|
||||
2. **Expose the dashboard to the tailnet only (port 8443):**
|
||||
```bash
|
||||
# If bound to localhost (127.0.0.1 or 0.0.0.0):
|
||||
tailscale serve --bg --https 8443 http://127.0.0.1:18789
|
||||
|
||||
# If bound to Tailscale IP only (e.g., 100.106.161.80):
|
||||
tailscale serve --bg --https 8443 http://100.106.161.80:18789
|
||||
```
|
||||
|
||||
3. **Expose only the webhook path publicly:**
|
||||
```bash
|
||||
# If bound to localhost (127.0.0.1 or 0.0.0.0):
|
||||
tailscale funnel --bg --set-path /googlechat http://127.0.0.1:18789/googlechat
|
||||
|
||||
# If bound to Tailscale IP only (e.g., 100.106.161.80):
|
||||
tailscale funnel --bg --set-path /googlechat http://100.106.161.80:18789/googlechat
|
||||
```
|
||||
|
||||
4. **Authorize the node for Funnel access:**
|
||||
If prompted, visit the authorization URL shown in the output to enable Funnel for this node in your tailnet policy.
|
||||
|
||||
5. **Verify the configuration:**
|
||||
```bash
|
||||
tailscale serve status
|
||||
tailscale funnel status
|
||||
```
|
||||
|
||||
Your public webhook URL will be:
|
||||
`https://<node-name>.<tailnet>.ts.net/googlechat`
|
||||
|
||||
Your private dashboard stays tailnet-only:
|
||||
`https://<node-name>.<tailnet>.ts.net:8443/`
|
||||
|
||||
Use the public URL (without `:8443`) in the Google Chat app config.
|
||||
|
||||
> Note: This configuration persists across reboots. To remove it later, run `tailscale funnel reset` and `tailscale serve reset`.
|
||||
|
||||
### Option B: Reverse Proxy (Caddy)
|
||||
If you use a reverse proxy like Caddy, only proxy the specific path:
|
||||
```text
|
||||
your-domain.com {
|
||||
reverse_proxy /googlechat* localhost:18789
|
||||
}
|
||||
```
|
||||
With this config, any request to `your-domain.com/` will be ignored or returned as 404, while `your-domain.com/googlechat` is safely routed to Bot.
|
||||
|
||||
### Option C: Cloudflare Tunnel
|
||||
Configure your tunnel's ingress rules to only route the webhook path:
|
||||
- **Path**: `/googlechat` -> `http://localhost:18789/googlechat`
|
||||
- **Default Rule**: HTTP 404 (Not Found)
|
||||
|
||||
## How it works
|
||||
|
||||
1. Google Chat sends webhook POSTs to the gateway. Each request includes an `Authorization: Bearer <token>` header.
|
||||
2. Bot verifies the token against the configured `audienceType` + `audience`:
|
||||
- `audienceType: "app-url"` → audience is your HTTPS webhook URL.
|
||||
- `audienceType: "project-number"` → audience is the Cloud project number.
|
||||
3. Messages are routed by space:
|
||||
- DMs use session key `agent:<agentId>:googlechat:dm:<spaceId>`.
|
||||
- Spaces use session key `agent:<agentId>:googlechat:group:<spaceId>`.
|
||||
4. DM access is pairing by default. Unknown senders receive a pairing code; approve with:
|
||||
- `bot pairing approve googlechat <code>`
|
||||
5. Group spaces require @-mention by default. Use `botUser` if mention detection needs the app’s user name.
|
||||
|
||||
## Targets
|
||||
Use these identifiers for delivery and allowlists:
|
||||
- Direct messages: `users/<userId>` or `users/<email>` (email addresses are accepted).
|
||||
- Spaces: `spaces/<spaceId>`.
|
||||
|
||||
## Config highlights
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
"googlechat": {
|
||||
enabled: true,
|
||||
serviceAccountFile: "/path/to/service-account.json",
|
||||
audienceType: "app-url",
|
||||
audience: "https://gateway.example.com/googlechat",
|
||||
webhookPath: "/googlechat",
|
||||
botUser: "users/1234567890", // optional; helps mention detection
|
||||
dm: {
|
||||
policy: "pairing",
|
||||
allowFrom: ["users/1234567890", "[email protected]"]
|
||||
},
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
"spaces/AAAA": {
|
||||
allow: true,
|
||||
requireMention: true,
|
||||
users: ["users/1234567890"],
|
||||
systemPrompt: "Short answers only."
|
||||
}
|
||||
},
|
||||
actions: { reactions: true },
|
||||
typingIndicator: "message",
|
||||
mediaMaxMb: 20
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Service account credentials can also be passed inline with `serviceAccount` (JSON string).
|
||||
- Default webhook path is `/googlechat` if `webhookPath` isn’t set.
|
||||
- Reactions are available via the `reactions` tool and `channels action` when `actions.reactions` is enabled.
|
||||
- `typingIndicator` supports `none`, `message` (default), and `reaction` (reaction requires user OAuth).
|
||||
- Attachments are downloaded through the Chat API and stored in the media pipeline (size capped by `mediaMaxMb`).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 405 Method Not Allowed
|
||||
If Google Cloud Logs Explorer shows errors like:
|
||||
```
|
||||
status code: 405, reason phrase: HTTP error response: HTTP/1.1 405 Method Not Allowed
|
||||
```
|
||||
|
||||
This means the webhook handler isn't registered. Common causes:
|
||||
1. **Channel not configured**: The `channels.googlechat` section is missing from your config. Verify with:
|
||||
```bash
|
||||
bot config get channels.googlechat
|
||||
```
|
||||
If it returns "Config path not found", add the configuration (see [Config highlights](#config-highlights)).
|
||||
|
||||
2. **Plugin not enabled**: Check plugin status:
|
||||
```bash
|
||||
bot plugins list | grep googlechat
|
||||
```
|
||||
If it shows "disabled", add `plugins.entries.googlechat.enabled: true` to your config.
|
||||
|
||||
3. **Gateway not restarted**: After adding config, restart the gateway:
|
||||
```bash
|
||||
bot gateway restart
|
||||
```
|
||||
|
||||
Verify the channel is running:
|
||||
```bash
|
||||
bot channels status
|
||||
# Should show: Google Chat default: enabled, configured, ...
|
||||
```
|
||||
|
||||
### Other issues
|
||||
- Check `bot channels status --probe` for auth errors or missing audience config.
|
||||
- If no messages arrive, confirm the Chat app's webhook URL + event subscriptions.
|
||||
- If mention gating blocks replies, set `botUser` to the app's user resource name and verify `requireMention`.
|
||||
- Use `bot logs --follow` while sending a test message to see if requests reach the gateway.
|
||||
|
||||
Related docs:
|
||||
- [Gateway configuration](/gateway/configuration)
|
||||
- [Security](/gateway/security)
|
||||
- [Reactions](/tools/reactions)
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "grammY Integration (Telegram Bot API)"
|
||||
description: "Telegram Bot API integration via grammY with setup notes"
|
||||
---
|
||||
|
||||
- TS-first Bot API client with built-in long-poll + webhook helpers, middleware, error handling, rate limiter.
|
||||
- Cleaner media helpers than hand-rolling fetch + FormData; supports all Bot API methods.
|
||||
- Extensible: proxy support via custom fetch, session middleware (optional), type-safe context.
|
||||
|
||||
# What we shipped
|
||||
- **Single client path:** fetch-based implementation removed; grammY is now the sole Telegram client (send + gateway) with the grammY throttler enabled by default.
|
||||
- **Gateway:** `monitorTelegramProvider` builds a grammY `Bot`, wires mention/allowlist gating, media download via `getFile`/`download`, and delivers replies with `sendMessage/sendPhoto/sendVideo/sendAudio/sendDocument`. Supports long-poll or webhook via `webhookCallback`.
|
||||
- **Proxy:** optional `channels.telegram.proxy` uses `undici.ProxyAgent` through grammY’s `client.baseFetch`.
|
||||
- **Webhook support:** `webhook-set.ts` wraps `setWebhook/deleteWebhook`; `webhook.ts` hosts the callback with health + graceful shutdown. Gateway enables webhook mode when `channels.telegram.webhookUrl` is set (otherwise it long-polls).
|
||||
- **Sessions:** direct chats collapse into the agent main session (`agent:<agentId>:<mainKey>`); groups use `agent:<agentId>:telegram:group:<chatId>`; replies route back to the same channel.
|
||||
- **Config knobs:** `channels.telegram.botToken`, `channels.telegram.dmPolicy`, `channels.telegram.groups` (allowlist + mention defaults), `channels.telegram.allowFrom`, `channels.telegram.groupAllowFrom`, `channels.telegram.groupPolicy`, `channels.telegram.mediaMaxMb`, `channels.telegram.linkPreview`, `channels.telegram.proxy`, `channels.telegram.webhookSecret`, `channels.telegram.webhookUrl`.
|
||||
- **Draft streaming:** optional `channels.telegram.streamMode` uses `sendMessageDraft` in private topic chats (Bot API 9.3+). This is separate from channel block streaming.
|
||||
- **Tests:** grammy mocks cover DM + group mention gating and outbound send; more media/webhook fixtures still welcome.
|
||||
|
||||
Open questions
|
||||
- Optional grammY plugins (throttler) if we hit Bot API 429s.
|
||||
- Add more structured media tests (stickers, voice notes).
|
||||
- Make webhook listen port configurable (currently fixed to 8787 unless wired through the gateway).
|
||||
@@ -0,0 +1,257 @@
|
||||
---
|
||||
title: "iMessage (imsg)"
|
||||
description: "iMessage support via imsg (JSON-RPC over stdio), setup, and chat_id routing"
|
||||
---
|
||||
|
||||
Status: external CLI integration. Gateway spawns `imsg rpc` (JSON-RPC over stdio).
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Ensure Messages is signed in on this Mac.
|
||||
2) Install `imsg`:
|
||||
- `brew install steipete/tap/imsg`
|
||||
3) Configure Bot with `channels.imessage.cliPath` and `channels.imessage.dbPath`.
|
||||
4) Start the gateway and approve any macOS prompts (Automation + Full Disk Access).
|
||||
|
||||
Minimal config:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
imessage: {
|
||||
enabled: true,
|
||||
cliPath: "/usr/local/bin/imsg",
|
||||
dbPath: "/Users/<you>/Library/Messages/chat.db"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## What it is
|
||||
- iMessage channel backed by `imsg` on macOS.
|
||||
- Deterministic routing: replies always go back to iMessage.
|
||||
- DMs share the agent's main session; groups are isolated (`agent:<agentId>:imessage:group:<chat_id>`).
|
||||
- If a multi-participant thread arrives with `is_group=false`, you can still isolate it by `chat_id` using `channels.imessage.groups` (see “Group-ish threads” below).
|
||||
|
||||
## Config writes
|
||||
By default, iMessage is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||
|
||||
Disable with:
|
||||
```json5
|
||||
{
|
||||
channels: { imessage: { configWrites: false } }
|
||||
}
|
||||
```
|
||||
|
||||
## Requirements
|
||||
- macOS with Messages signed in.
|
||||
- Full Disk Access for Bot + `imsg` (Messages DB access).
|
||||
- Automation permission when sending.
|
||||
- `channels.imessage.cliPath` can point to any command that proxies stdin/stdout (for example, a wrapper script that SSHes to another Mac and runs `imsg rpc`).
|
||||
|
||||
## Setup (fast path)
|
||||
1) Ensure Messages is signed in on this Mac.
|
||||
2) Configure iMessage and start the gateway.
|
||||
|
||||
### Dedicated bot macOS user (for isolated identity)
|
||||
If you want the bot to send from a **separate iMessage identity** (and keep your personal Messages clean), use a dedicated Apple ID + a dedicated macOS user.
|
||||
|
||||
1) Create a dedicated Apple ID (example: `[email protected]`).
|
||||
- Apple may require a phone number for verification / 2FA.
|
||||
2) Create a macOS user (example: `botshome`) and sign into it.
|
||||
3) Open Messages in that macOS user and sign into iMessage using the bot Apple ID.
|
||||
4) Enable Remote Login (System Settings → General → Sharing → Remote Login).
|
||||
5) Install `imsg`:
|
||||
- `brew install steipete/tap/imsg`
|
||||
6) Set up SSH so `ssh <bot-macos-user>@localhost true` works without a password.
|
||||
7) Point `channels.imessage.accounts.bot.cliPath` at an SSH wrapper that runs `imsg` as the bot user.
|
||||
|
||||
First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the *bot macOS user*. If `imsg rpc` looks stuck or exits, log into that user (Screen Sharing helps), run a one-time `imsg chats --limit 1` / `imsg send ...`, approve prompts, then retry.
|
||||
|
||||
Example wrapper (`chmod +x`). Replace `<bot-macos-user>` with your actual macOS username:
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Run an interactive SSH once first to accept host keys:
|
||||
# ssh <bot-macos-user>@localhost true
|
||||
exec /usr/bin/ssh -o BatchMode=yes -o ConnectTimeout=5 -T <bot-macos-user>@localhost \
|
||||
"/usr/local/bin/imsg" "$@"
|
||||
```
|
||||
|
||||
Example config:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
imessage: {
|
||||
enabled: true,
|
||||
accounts: {
|
||||
bot: {
|
||||
name: "Bot",
|
||||
enabled: true,
|
||||
cliPath: "/path/to/imsg-bot",
|
||||
dbPath: "/Users/<bot-macos-user>/Library/Messages/chat.db"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For single-account setups, use flat options (`channels.imessage.cliPath`, `channels.imessage.dbPath`) instead of the `accounts` map.
|
||||
|
||||
### Remote/SSH variant (optional)
|
||||
If you want iMessage on another Mac, set `channels.imessage.cliPath` to a wrapper that runs `imsg` on the remote macOS host over SSH. Bot only needs stdio.
|
||||
|
||||
Example wrapper:
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
exec ssh -T gateway-host imsg "$@"
|
||||
```
|
||||
|
||||
**Remote attachments:** When `cliPath` points to a remote host via SSH, attachment paths in the Messages database reference files on the remote machine. Bot can automatically fetch these over SCP by setting `channels.imessage.remoteHost`:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
imessage: {
|
||||
cliPath: "~/imsg-ssh", // SSH wrapper to remote Mac
|
||||
remoteHost: "user@gateway-host", // for SCP file transfer
|
||||
includeAttachments: true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If `remoteHost` is not set, Bot attempts to auto-detect it by parsing the SSH command in your wrapper script. Explicit configuration is recommended for reliability.
|
||||
|
||||
#### Remote Mac via Tailscale (example)
|
||||
If the Gateway runs on a Linux host/VM but iMessage must run on a Mac, Tailscale is the simplest bridge: the Gateway talks to the Mac over the tailnet, runs `imsg` via SSH, and SCPs attachments back.
|
||||
|
||||
Architecture:
|
||||
```
|
||||
┌──────────────────────────────┐ SSH (imsg rpc) ┌──────────────────────────┐
|
||||
│ Gateway host (Linux/VM) │──────────────────────────────────▶│ Mac with Messages + imsg │
|
||||
│ - bot gateway │ SCP (attachments) │ - Messages signed in │
|
||||
│ - channels.imessage.cliPath │◀──────────────────────────────────│ - Remote Login enabled │
|
||||
└──────────────────────────────┘ └──────────────────────────┘
|
||||
▲
|
||||
│ Tailscale tailnet (hostname or 100.x.y.z)
|
||||
▼
|
||||
user@gateway-host
|
||||
```
|
||||
|
||||
Concrete config example (Tailscale hostname):
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
imessage: {
|
||||
enabled: true,
|
||||
cliPath: "~/.bot/scripts/imsg-ssh",
|
||||
remoteHost: "[email protected]",
|
||||
includeAttachments: true,
|
||||
dbPath: "/Users/bot/Library/Messages/chat.db"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example wrapper (`~/.bot/scripts/imsg-ssh`):
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
exec ssh -T [email protected] imsg "$@"
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Ensure the Mac is signed in to Messages, and Remote Login is enabled.
|
||||
- Use SSH keys so `ssh [email protected]` works without prompts.
|
||||
- `remoteHost` should match the SSH target so SCP can fetch attachments.
|
||||
|
||||
Multi-account support: use `channels.imessage.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern. Don't commit `~/.bot/bot.json` (it often contains tokens).
|
||||
|
||||
## Access control (DMs + groups)
|
||||
DMs:
|
||||
- Default: `channels.imessage.dmPolicy = "pairing"`.
|
||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
- `bot pairing list imessage`
|
||||
- `bot pairing approve imessage <CODE>`
|
||||
- Pairing is the default token exchange for iMessage DMs. Details: [Pairing](/start/pairing)
|
||||
|
||||
Groups:
|
||||
- `channels.imessage.groupPolicy = open | allowlist | disabled`.
|
||||
- `channels.imessage.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||
- Mention gating uses `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) because iMessage has no native mention metadata.
|
||||
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
||||
|
||||
## How it works (behavior)
|
||||
- `imsg` streams message events; the gateway normalizes them into the shared channel envelope.
|
||||
- Replies always route back to the same chat id or handle.
|
||||
|
||||
## Group-ish threads (`is_group=false`)
|
||||
Some iMessage threads can have multiple participants but still arrive with `is_group=false` depending on how Messages stores the chat identifier.
|
||||
|
||||
If you explicitly configure a `chat_id` under `channels.imessage.groups`, Bot treats that thread as a “group” for:
|
||||
- session isolation (separate `agent:<agentId>:imessage:group:<chat_id>` session key)
|
||||
- group allowlisting / mention gating behavior
|
||||
|
||||
Example:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
imessage: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15555550123"],
|
||||
groups: {
|
||||
"42": { "requireMention": false }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
This is useful when you want an isolated personality/model for a specific thread (see [Multi-agent routing](/concepts/multi-agent)). For filesystem isolation, see [Sandboxing](/gateway/sandboxing).
|
||||
|
||||
## Media + limits
|
||||
- Optional attachment ingestion via `channels.imessage.includeAttachments`.
|
||||
- Media cap via `channels.imessage.mediaMaxMb`.
|
||||
|
||||
## Limits
|
||||
- Outbound text is chunked to `channels.imessage.textChunkLimit` (default 4000).
|
||||
- Optional newline chunking: set `channels.imessage.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- Media uploads are capped by `channels.imessage.mediaMaxMb` (default 16).
|
||||
|
||||
## Addressing / delivery targets
|
||||
Prefer `chat_id` for stable routing:
|
||||
- `chat_id:123` (preferred)
|
||||
- `chat_guid:...`
|
||||
- `chat_identifier:...`
|
||||
- direct handles: `imessage:+1555` / `sms:+1555` / `[email protected]`
|
||||
|
||||
List chats:
|
||||
```
|
||||
imsg chats --limit 20
|
||||
```
|
||||
|
||||
## Configuration reference (iMessage)
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
- `channels.imessage.enabled`: enable/disable channel startup.
|
||||
- `channels.imessage.cliPath`: path to `imsg`.
|
||||
- `channels.imessage.dbPath`: Messages DB path.
|
||||
- `channels.imessage.remoteHost`: SSH host for SCP attachment transfer when `cliPath` points to a remote Mac (e.g., `user@gateway-host`). Auto-detected from SSH wrapper if not set.
|
||||
- `channels.imessage.service`: `imessage | sms | auto`.
|
||||
- `channels.imessage.region`: SMS region.
|
||||
- `channels.imessage.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing).
|
||||
- `channels.imessage.allowFrom`: DM allowlist (handles, emails, E.164 numbers, or `chat_id:*`). `open` requires `"*"`. iMessage has no usernames; use handles or chat targets.
|
||||
- `channels.imessage.groupPolicy`: `open | allowlist | disabled` (default: allowlist).
|
||||
- `channels.imessage.groupAllowFrom`: group sender allowlist.
|
||||
- `channels.imessage.historyLimit` / `channels.imessage.accounts.*.historyLimit`: max group messages to include as context (0 disables).
|
||||
- `channels.imessage.dmHistoryLimit`: DM history limit in user turns. Per-user overrides: `channels.imessage.dms["<handle>"].historyLimit`.
|
||||
- `channels.imessage.groups`: per-group defaults + allowlist (use `"*"` for global defaults).
|
||||
- `channels.imessage.includeAttachments`: ingest attachments into context.
|
||||
- `channels.imessage.mediaMaxMb`: inbound/outbound media cap (MB).
|
||||
- `channels.imessage.textChunkLimit`: outbound chunk size (chars).
|
||||
- `channels.imessage.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
|
||||
Related global options:
|
||||
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`).
|
||||
- `messages.responsePrefix`.
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: "Chat Channels"
|
||||
description: "Messaging platforms Bot can connect to"
|
||||
---
|
||||
|
||||
Bot can talk to you on any chat app you already use. Each channel connects via the Gateway.
|
||||
Text is supported everywhere; media and reactions vary by channel.
|
||||
|
||||
## Supported channels
|
||||
|
||||
- [WhatsApp](/channels/whatsapp) — Most popular; uses Baileys and requires QR pairing.
|
||||
- [Telegram](/channels/telegram) — Bot API via grammY; supports groups.
|
||||
- [Discord](/channels/discord) — Discord Bot API + Gateway; supports servers, channels, and DMs.
|
||||
- [Slack](/channels/slack) — Bolt SDK; workspace apps.
|
||||
- [Google Chat](/channels/googlechat) — Google Chat API app via HTTP webhook.
|
||||
- [Mattermost](/channels/mattermost) — Bot API + WebSocket; channels, groups, DMs (plugin, installed separately).
|
||||
- [Signal](/channels/signal) — signal-cli; privacy-focused.
|
||||
- [BlueBubbles](/channels/bluebubbles) — **Recommended for iMessage**; uses the BlueBubbles macOS server REST API with full feature support (edit, unsend, effects, reactions, group management — edit currently broken on macOS 26 Tahoe).
|
||||
- [iMessage](/channels/imessage) — macOS only; native integration via imsg (legacy, consider BlueBubbles for new setups).
|
||||
- [Microsoft Teams](/channels/msteams) — Bot Framework; enterprise support (plugin, installed separately).
|
||||
- [Nextcloud Talk](/channels/nextcloud-talk) — Self-hosted chat via Nextcloud Talk (plugin, installed separately).
|
||||
- [Matrix](/channels/matrix) — Matrix protocol (plugin, installed separately).
|
||||
- [Nostr](/channels/nostr) — Decentralized DMs via NIP-04 (plugin, installed separately).
|
||||
- [Tlon](/channels/tlon) — Urbit-based messenger (plugin, installed separately).
|
||||
- [Zalo](/channels/zalo) — Zalo Bot API; Vietnam's popular messenger (plugin, installed separately).
|
||||
- [Zalo Personal](/channels/zalouser) — Zalo personal account via QR login (plugin, installed separately).
|
||||
- [WebChat](/web/webchat) — Gateway WebChat UI over WebSocket.
|
||||
|
||||
## Notes
|
||||
|
||||
- Channels can run simultaneously; configure multiple and Bot will route per chat.
|
||||
- Fastest setup is usually **Telegram** (simple bot token). WhatsApp requires QR pairing and
|
||||
stores more state on disk.
|
||||
- Group behavior varies by channel; see [Groups](/concepts/groups).
|
||||
- DM pairing and allowlists are enforced for safety; see [Security](/gateway/security).
|
||||
- Telegram internals: [grammY notes](/channels/grammy).
|
||||
- Troubleshooting: [Channel troubleshooting](/channels/troubleshooting).
|
||||
- Model providers are documented separately; see [Model Providers](/providers/models).
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "Channel location parsing"
|
||||
description: "Inbound channel location parsing (Telegram + WhatsApp) and context fields"
|
||||
---
|
||||
|
||||
Bot normalizes shared locations from chat channels into:
|
||||
- human-readable text appended to the inbound body, and
|
||||
- structured fields in the auto-reply context payload.
|
||||
|
||||
Currently supported:
|
||||
- **Telegram** (location pins + venues + live locations)
|
||||
- **WhatsApp** (locationMessage + liveLocationMessage)
|
||||
- **Matrix** (`m.location` with `geo_uri`)
|
||||
|
||||
## Text formatting
|
||||
Locations are rendered as friendly lines without brackets:
|
||||
|
||||
- Pin:
|
||||
- `📍 48.858844, 2.294351 ±12m`
|
||||
- Named place:
|
||||
- `📍 Eiffel Tower — Champ de Mars, Paris (48.858844, 2.294351 ±12m)`
|
||||
- Live share:
|
||||
- `🛰 Live location: 48.858844, 2.294351 ±12m`
|
||||
|
||||
If the channel includes a caption/comment, it is appended on the next line:
|
||||
```
|
||||
📍 48.858844, 2.294351 ±12m
|
||||
Meet here
|
||||
```
|
||||
|
||||
## Context fields
|
||||
When a location is present, these fields are added to `ctx`:
|
||||
- `LocationLat` (number)
|
||||
- `LocationLon` (number)
|
||||
- `LocationAccuracy` (number, meters; optional)
|
||||
- `LocationName` (string; optional)
|
||||
- `LocationAddress` (string; optional)
|
||||
- `LocationSource` (`pin | place | live`)
|
||||
- `LocationIsLive` (boolean)
|
||||
|
||||
## Channel notes
|
||||
- **Telegram**: venues map to `LocationName/LocationAddress`; live locations use `live_period`.
|
||||
- **WhatsApp**: `locationMessage.comment` and `liveLocationMessage.caption` are appended as the caption line.
|
||||
- **Matrix**: `geo_uri` is parsed as a pin location; altitude is ignored and `LocationIsLive` is always false.
|
||||
@@ -0,0 +1,228 @@
|
||||
---
|
||||
title: "Matrix (plugin)"
|
||||
description: "Matrix support status, capabilities, and configuration"
|
||||
---
|
||||
|
||||
Matrix is an open, decentralized messaging protocol. Bot connects as a Matrix **user**
|
||||
on any homeserver, so you need a Matrix account for the bot. Once it is logged in, you can DM
|
||||
the bot directly or invite it to rooms (Matrix "groups"). Beeper is a valid client option too,
|
||||
but it requires E2EE to be enabled.
|
||||
|
||||
Status: supported via plugin (matrix-bot-sdk). Direct messages, rooms, threads, media, reactions,
|
||||
polls (send + poll-start as text), location, and E2EE (with crypto support).
|
||||
|
||||
## Plugin required
|
||||
|
||||
Matrix ships as a plugin and is not bundled with the core install.
|
||||
|
||||
Install via CLI (npm registry):
|
||||
|
||||
```bash
|
||||
bot plugins install @bot/matrix
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
bot plugins install ./extensions/matrix
|
||||
```
|
||||
|
||||
If you choose Matrix during configure/onboarding and a git checkout is detected,
|
||||
Bot will offer the local install path automatically.
|
||||
|
||||
Details: [Plugins](/plugin)
|
||||
|
||||
## Setup
|
||||
|
||||
1) Install the Matrix plugin:
|
||||
- From npm: `bot plugins install @bot/matrix`
|
||||
- From a local checkout: `bot plugins install ./extensions/matrix`
|
||||
2) Create a Matrix account on a homeserver:
|
||||
- Browse hosting options at [https://matrix.org/ecosystem/hosting/](https://matrix.org/ecosystem/hosting/)
|
||||
- Or host it yourself.
|
||||
3) Get an access token for the bot account:
|
||||
- Use the Matrix login API with `curl` at your home server:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url https://matrix.example.org/_matrix/client/v3/login \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"type": "m.login.password",
|
||||
"identifier": {
|
||||
"type": "m.id.user",
|
||||
"user": "your-user-name"
|
||||
},
|
||||
"password": "your-password"
|
||||
}'
|
||||
```
|
||||
|
||||
- Replace `matrix.example.org` with your homeserver URL.
|
||||
- Or set `channels.matrix.userId` + `channels.matrix.password`: Bot calls the same
|
||||
login endpoint, stores the access token in `~/.bot/credentials/matrix/credentials.json`,
|
||||
and reuses it on next start.
|
||||
4) Configure credentials:
|
||||
- Env: `MATRIX_HOMESERVER`, `MATRIX_ACCESS_TOKEN` (or `MATRIX_USER_ID` + `MATRIX_PASSWORD`)
|
||||
- Or config: `channels.matrix.*`
|
||||
- If both are set, config takes precedence.
|
||||
- With access token: user ID is fetched automatically via `/whoami`.
|
||||
- When set, `channels.matrix.userId` should be the full Matrix ID (example: `@bot:example.org`).
|
||||
5) Restart the gateway (or finish onboarding).
|
||||
6) Start a DM with the bot or invite it to a room from any Matrix client
|
||||
(Element, Beeper, etc.; see https://matrix.org/ecosystem/clients/). Beeper requires E2EE,
|
||||
so set `channels.matrix.encryption: true` and verify the device.
|
||||
|
||||
Minimal config (access token, user ID auto-fetched):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
matrix: {
|
||||
enabled: true,
|
||||
homeserver: "https://matrix.example.org",
|
||||
accessToken: "syt_***",
|
||||
dm: { policy: "pairing" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
E2EE config (end to end encryption enabled):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
matrix: {
|
||||
enabled: true,
|
||||
homeserver: "https://matrix.example.org",
|
||||
accessToken: "syt_***",
|
||||
encryption: true,
|
||||
dm: { policy: "pairing" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Encryption (E2EE)
|
||||
|
||||
End-to-end encryption is **supported** via the Rust crypto SDK.
|
||||
|
||||
Enable with `channels.matrix.encryption: true`:
|
||||
|
||||
- If the crypto module loads, encrypted rooms are decrypted automatically.
|
||||
- Outbound media is encrypted when sending to encrypted rooms.
|
||||
- On first connection, Bot requests device verification from your other sessions.
|
||||
- Verify the device in another Matrix client (Element, etc.) to enable key sharing.
|
||||
- If the crypto module cannot be loaded, E2EE is disabled and encrypted rooms will not decrypt;
|
||||
Bot logs a warning.
|
||||
- If you see missing crypto module errors (for example, `@matrix-org/matrix-sdk-crypto-nodejs-*`),
|
||||
allow build scripts for `@matrix-org/matrix-sdk-crypto-nodejs` and run
|
||||
`pnpm rebuild @matrix-org/matrix-sdk-crypto-nodejs` or fetch the binary with
|
||||
`node node_modules/@matrix-org/matrix-sdk-crypto-nodejs/download-lib.js`.
|
||||
|
||||
Crypto state is stored per account + access token in
|
||||
`~/.bot/matrix/accounts/<account>/<homeserver>__<user>/<token-hash>/crypto/`
|
||||
(SQLite database). Sync state lives alongside it in `bot-storage.json`.
|
||||
If the access token (device) changes, a new store is created and the bot must be
|
||||
re-verified for encrypted rooms.
|
||||
|
||||
**Device verification:**
|
||||
When E2EE is enabled, the bot will request verification from your other sessions on startup.
|
||||
Open Element (or another client) and approve the verification request to establish trust.
|
||||
Once verified, the bot can decrypt messages in encrypted rooms.
|
||||
|
||||
## Routing model
|
||||
|
||||
- Replies always go back to Matrix.
|
||||
- DMs share the agent's main session; rooms map to group sessions.
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
- Default: `channels.matrix.dm.policy = "pairing"`. Unknown senders get a pairing code.
|
||||
- Approve via:
|
||||
- `bot pairing list matrix`
|
||||
- `bot pairing approve matrix <CODE>`
|
||||
- Public DMs: `channels.matrix.dm.policy="open"` plus `channels.matrix.dm.allowFrom=["*"]`.
|
||||
- `channels.matrix.dm.allowFrom` accepts user IDs or display names. The wizard resolves display names to user IDs when directory search is available.
|
||||
|
||||
## Rooms (groups)
|
||||
|
||||
- Default: `channels.matrix.groupPolicy = "allowlist"` (mention-gated). Use `channels.defaults.groupPolicy` to override the default when unset.
|
||||
- Allowlist rooms with `channels.matrix.groups` (room IDs, aliases, or names):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
matrix: {
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
"!roomId:example.org": { allow: true },
|
||||
"#alias:example.org": { allow: true }
|
||||
},
|
||||
groupAllowFrom: ["@owner:example.org"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `requireMention: false` enables auto-reply in that room.
|
||||
- `groups."*"` can set defaults for mention gating across rooms.
|
||||
- `groupAllowFrom` restricts which senders can trigger the bot in rooms (optional).
|
||||
- Per-room `users` allowlists can further restrict senders inside a specific room.
|
||||
- The configure wizard prompts for room allowlists (room IDs, aliases, or names) and resolves names when possible.
|
||||
- On startup, Bot resolves room/user names in allowlists to IDs and logs the mapping; unresolved entries are kept as typed.
|
||||
- Invites are auto-joined by default; control with `channels.matrix.autoJoin` and `channels.matrix.autoJoinAllowlist`.
|
||||
- To allow **no rooms**, set `channels.matrix.groupPolicy: "disabled"` (or keep an empty allowlist).
|
||||
- Legacy key: `channels.matrix.rooms` (same shape as `groups`).
|
||||
|
||||
## Threads
|
||||
|
||||
- Reply threading is supported.
|
||||
- `channels.matrix.threadReplies` controls whether replies stay in threads:
|
||||
- `off`, `inbound` (default), `always`
|
||||
- `channels.matrix.replyToMode` controls reply-to metadata when not replying in a thread:
|
||||
- `off` (default), `first`, `all`
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Feature | Status |
|
||||
|---------|--------|
|
||||
| Direct messages | ✅ Supported |
|
||||
| Rooms | ✅ Supported |
|
||||
| Threads | ✅ Supported |
|
||||
| Media | ✅ Supported |
|
||||
| E2EE | ✅ Supported (crypto module required) |
|
||||
| Reactions | ✅ Supported (send/read via tools) |
|
||||
| Polls | ✅ Send supported; inbound poll starts are converted to text (responses/ends ignored) |
|
||||
| Location | ✅ Supported (geo URI; altitude ignored) |
|
||||
| Native commands | ✅ Supported |
|
||||
|
||||
## Configuration reference (Matrix)
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.matrix.enabled`: enable/disable channel startup.
|
||||
- `channels.matrix.homeserver`: homeserver URL.
|
||||
- `channels.matrix.userId`: Matrix user ID (optional with access token).
|
||||
- `channels.matrix.accessToken`: access token.
|
||||
- `channels.matrix.password`: password for login (token stored).
|
||||
- `channels.matrix.deviceName`: device display name.
|
||||
- `channels.matrix.encryption`: enable E2EE (default: false).
|
||||
- `channels.matrix.initialSyncLimit`: initial sync limit.
|
||||
- `channels.matrix.threadReplies`: `off | inbound | always` (default: inbound).
|
||||
- `channels.matrix.textChunkLimit`: outbound text chunk size (chars).
|
||||
- `channels.matrix.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.matrix.dm.policy`: `pairing | allowlist | open | disabled` (default: pairing).
|
||||
- `channels.matrix.dm.allowFrom`: DM allowlist (user IDs or display names). `open` requires `"*"`. The wizard resolves names to IDs when possible.
|
||||
- `channels.matrix.groupPolicy`: `allowlist | open | disabled` (default: allowlist).
|
||||
- `channels.matrix.groupAllowFrom`: allowlisted senders for group messages.
|
||||
- `channels.matrix.allowlistOnly`: force allowlist rules for DMs + rooms.
|
||||
- `channels.matrix.groups`: group allowlist + per-room settings map.
|
||||
- `channels.matrix.rooms`: legacy group allowlist/config.
|
||||
- `channels.matrix.replyToMode`: reply-to mode for threads/tags.
|
||||
- `channels.matrix.mediaMaxMb`: inbound/outbound media cap (MB).
|
||||
- `channels.matrix.autoJoin`: invite handling (`always | allowlist | off`, default: always).
|
||||
- `channels.matrix.autoJoinAllowlist`: allowed room IDs/aliases for auto-join.
|
||||
- `channels.matrix.actions`: per-action tool gating (reactions/messages/pins/memberInfo/channelInfo).
|
||||
@@ -0,0 +1,119 @@
|
||||
---
|
||||
title: "Mattermost (plugin)"
|
||||
description: "Mattermost bot setup and Bot config"
|
||||
---
|
||||
|
||||
Status: supported via plugin (bot token + WebSocket events). Channels, groups, and DMs are supported.
|
||||
Mattermost is a self-hostable team messaging platform; see the official site at
|
||||
[mattermost.com](https://mattermost.com) for product details and downloads.
|
||||
|
||||
## Plugin required
|
||||
Mattermost ships as a plugin and is not bundled with the core install.
|
||||
|
||||
Install via CLI (npm registry):
|
||||
```bash
|
||||
bot plugins install @bot/mattermost
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
```bash
|
||||
bot plugins install ./extensions/mattermost
|
||||
```
|
||||
|
||||
If you choose Mattermost during configure/onboarding and a git checkout is detected,
|
||||
Bot will offer the local install path automatically.
|
||||
|
||||
Details: [Plugins](/plugin)
|
||||
|
||||
## Quick setup
|
||||
1) Install the Mattermost plugin.
|
||||
2) Create a Mattermost bot account and copy the **bot token**.
|
||||
3) Copy the Mattermost **base URL** (e.g., `https://chat.example.com`).
|
||||
4) Configure Bot and start the gateway.
|
||||
|
||||
Minimal config:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
enabled: true,
|
||||
botToken: "mm-token",
|
||||
baseUrl: "https://chat.example.com",
|
||||
dmPolicy: "pairing"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment variables (default account)
|
||||
Set these on the gateway host if you prefer env vars:
|
||||
|
||||
- `MATTERMOST_BOT_TOKEN=...`
|
||||
- `MATTERMOST_URL=https://chat.example.com`
|
||||
|
||||
Env vars apply only to the **default** account (`default`). Other accounts must use config values.
|
||||
|
||||
## Chat modes
|
||||
Mattermost responds to DMs automatically. Channel behavior is controlled by `chatmode`:
|
||||
|
||||
- `oncall` (default): respond only when @mentioned in channels.
|
||||
- `onmessage`: respond to every channel message.
|
||||
- `onchar`: respond when a message starts with a trigger prefix.
|
||||
|
||||
Config example:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
chatmode: "onchar",
|
||||
oncharPrefixes: [">", "!"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `onchar` still responds to explicit @mentions.
|
||||
- `channels.mattermost.requireMention` is honored for legacy configs but `chatmode` is preferred.
|
||||
|
||||
## Access control (DMs)
|
||||
- Default: `channels.mattermost.dmPolicy = "pairing"` (unknown senders get a pairing code).
|
||||
- Approve via:
|
||||
- `bot pairing list mattermost`
|
||||
- `bot pairing approve mattermost <CODE>`
|
||||
- Public DMs: `channels.mattermost.dmPolicy="open"` plus `channels.mattermost.allowFrom=["*"]`.
|
||||
|
||||
## Channels (groups)
|
||||
- Default: `channels.mattermost.groupPolicy = "allowlist"` (mention-gated).
|
||||
- Allowlist senders with `channels.mattermost.groupAllowFrom` (user IDs or `@username`).
|
||||
- Open channels: `channels.mattermost.groupPolicy="open"` (mention-gated).
|
||||
|
||||
## Targets for outbound delivery
|
||||
Use these target formats with `bot message send` or cron/webhooks:
|
||||
|
||||
- `channel:<id>` for a channel
|
||||
- `user:<id>` for a DM
|
||||
- `@username` for a DM (resolved via the Mattermost API)
|
||||
|
||||
Bare IDs are treated as channels.
|
||||
|
||||
## Multi-account
|
||||
Mattermost supports multiple accounts under `channels.mattermost.accounts`:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
accounts: {
|
||||
default: { name: "Primary", botToken: "mm-token", baseUrl: "https://chat.example.com" },
|
||||
alerts: { name: "Alerts", botToken: "mm-token-2", baseUrl: "https://alerts.example.com" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
- No replies in channels: ensure the bot is in the channel and mention it (oncall), use a trigger prefix (onchar), or set `chatmode: "onmessage"`.
|
||||
- Auth errors: check the bot token, base URL, and whether the account is enabled.
|
||||
- Multi-account issues: env vars only apply to the `default` account.
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"title": "Channels",
|
||||
"pages": [
|
||||
"index",
|
||||
"whatsapp",
|
||||
"telegram",
|
||||
"grammy",
|
||||
"discord",
|
||||
"slack",
|
||||
"signal",
|
||||
"bluebubbles",
|
||||
"imessage",
|
||||
"matrix",
|
||||
"mattermost",
|
||||
"googlechat",
|
||||
"msteams",
|
||||
"nostr",
|
||||
"tlon",
|
||||
"nextcloud-talk",
|
||||
"zalo",
|
||||
"zalouser",
|
||||
"location",
|
||||
"troubleshooting"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,701 @@
|
||||
---
|
||||
title: "Microsoft Teams (plugin)"
|
||||
description: "Microsoft Teams bot support status, capabilities, and configuration"
|
||||
---
|
||||
|
||||
> "Abandon all hope, ye who enter here."
|
||||
|
||||
|
||||
Updated: 2026-01-21
|
||||
|
||||
Status: text + DM attachments are supported; channel/group file sending requires `sharePointSiteId` + Graph permissions (see [Sending files in group chats](#sending-files-in-group-chats)). Polls are sent via Adaptive Cards.
|
||||
|
||||
## Plugin required
|
||||
Microsoft Teams ships as a plugin and is not bundled with the core install.
|
||||
|
||||
**Breaking change (2026.1.15):** MS Teams moved out of core. If you use it, you must install the plugin.
|
||||
|
||||
Explainable: keeps core installs lighter and lets MS Teams dependencies update independently.
|
||||
|
||||
Install via CLI (npm registry):
|
||||
```bash
|
||||
bot plugins install @bot/msteams
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
```bash
|
||||
bot plugins install ./extensions/msteams
|
||||
```
|
||||
|
||||
If you choose Teams during configure/onboarding and a git checkout is detected,
|
||||
Bot will offer the local install path automatically.
|
||||
|
||||
Details: [Plugins](/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Install the Microsoft Teams plugin.
|
||||
2) Create an **Azure Bot** (App ID + client secret + tenant ID).
|
||||
3) Configure Bot with those credentials.
|
||||
4) Expose `/api/messages` (port 3978 by default) via a public URL or tunnel.
|
||||
5) Install the Teams app package and start the gateway.
|
||||
|
||||
Minimal config:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
enabled: true,
|
||||
appId: "<APP_ID>",
|
||||
appPassword: "<APP_PASSWORD>",
|
||||
tenantId: "<TENANT_ID>",
|
||||
webhook: { port: 3978, path: "/api/messages" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Note: group chats are blocked by default (`channels.msteams.groupPolicy: "allowlist"`). To allow group replies, set `channels.msteams.groupAllowFrom` (or use `groupPolicy: "open"` to allow any member, mention-gated).
|
||||
|
||||
## Goals
|
||||
- Talk to Bot via Teams DMs, group chats, or channels.
|
||||
- Keep routing deterministic: replies always go back to the channel they arrived on.
|
||||
- Default to safe channel behavior (mentions required unless configured otherwise).
|
||||
|
||||
## Config writes
|
||||
By default, Microsoft Teams is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||
|
||||
Disable with:
|
||||
```json5
|
||||
{
|
||||
channels: { msteams: { configWrites: false } }
|
||||
}
|
||||
```
|
||||
|
||||
## Access control (DMs + groups)
|
||||
|
||||
**DM access**
|
||||
- Default: `channels.msteams.dmPolicy = "pairing"`. Unknown senders are ignored until approved.
|
||||
- `channels.msteams.allowFrom` accepts AAD object IDs, UPNs, or display names. The wizard resolves names to IDs via Microsoft Graph when credentials allow.
|
||||
|
||||
**Group access**
|
||||
- Default: `channels.msteams.groupPolicy = "allowlist"` (blocked unless you add `groupAllowFrom`). Use `channels.defaults.groupPolicy` to override the default when unset.
|
||||
- `channels.msteams.groupAllowFrom` controls which senders can trigger in group chats/channels (falls back to `channels.msteams.allowFrom`).
|
||||
- Set `groupPolicy: "open"` to allow any member (still mention‑gated by default).
|
||||
- To allow **no channels**, set `channels.msteams.groupPolicy: "disabled"`.
|
||||
|
||||
Example:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["[email protected]"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Teams + channel allowlist**
|
||||
- Scope group/channel replies by listing teams and channels under `channels.msteams.teams`.
|
||||
- Keys can be team IDs or names; channel keys can be conversation IDs or names.
|
||||
- When `groupPolicy="allowlist"` and a teams allowlist is present, only listed teams/channels are accepted (mention‑gated).
|
||||
- The configure wizard accepts `Team/Channel` entries and stores them for you.
|
||||
- On startup, Bot resolves team/channel and user allowlist names to IDs (when Graph permissions allow)
|
||||
and logs the mapping; unresolved entries are kept as typed.
|
||||
|
||||
Example:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
groupPolicy: "allowlist",
|
||||
teams: {
|
||||
"My Team": {
|
||||
channels: {
|
||||
"General": { requireMention: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## How it works
|
||||
1. Install the Microsoft Teams plugin.
|
||||
2. Create an **Azure Bot** (App ID + secret + tenant ID).
|
||||
3. Build a **Teams app package** that references the bot and includes the RSC permissions below.
|
||||
4. Upload/install the Teams app into a team (or personal scope for DMs).
|
||||
5. Configure `msteams` in `~/.bot/bot.json` (or env vars) and start the gateway.
|
||||
6. The gateway listens for Bot Framework webhook traffic on `/api/messages` by default.
|
||||
|
||||
## Azure Bot Setup (Prerequisites)
|
||||
|
||||
Before configuring Bot, you need to create an Azure Bot resource.
|
||||
|
||||
### Step 1: Create Azure Bot
|
||||
|
||||
1. Go to [Create Azure Bot](https://portal.azure.com/#create/Microsoft.AzureBot)
|
||||
2. Fill in the **Basics** tab:
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Bot handle** | Your bot name, e.g., `bot-msteams` (must be unique) |
|
||||
| **Subscription** | Select your Azure subscription |
|
||||
| **Resource group** | Create new or use existing |
|
||||
| **Pricing tier** | **Free** for dev/testing |
|
||||
| **Type of App** | **Single Tenant** (recommended - see note below) |
|
||||
| **Creation type** | **Create new Microsoft App ID** |
|
||||
|
||||
> **Deprecation notice:** Creation of new multi-tenant bots was deprecated after 2025-07-31. Use **Single Tenant** for new bots.
|
||||
|
||||
3. Click **Review + create** → **Create** (wait ~1-2 minutes)
|
||||
|
||||
### Step 2: Get Credentials
|
||||
|
||||
1. Go to your Azure Bot resource → **Configuration**
|
||||
2. Copy **Microsoft App ID** → this is your `appId`
|
||||
3. Click **Manage Password** → go to the App Registration
|
||||
4. Under **Certificates & secrets** → **New client secret** → copy the **Value** → this is your `appPassword`
|
||||
5. Go to **Overview** → copy **Directory (tenant) ID** → this is your `tenantId`
|
||||
|
||||
### Step 3: Configure Messaging Endpoint
|
||||
|
||||
1. In Azure Bot → **Configuration**
|
||||
2. Set **Messaging endpoint** to your webhook URL:
|
||||
- Production: `https://your-domain.com/api/messages`
|
||||
- Local dev: Use a tunnel (see [Local Development](#local-development-tunneling) below)
|
||||
|
||||
### Step 4: Enable Teams Channel
|
||||
|
||||
1. In Azure Bot → **Channels**
|
||||
2. Click **Microsoft Teams** → Configure → Save
|
||||
3. Accept the Terms of Service
|
||||
|
||||
## Local Development (Tunneling)
|
||||
|
||||
Teams can't reach `localhost`. Use a tunnel for local development:
|
||||
|
||||
**Option A: ngrok**
|
||||
```bash
|
||||
ngrok http 3978
|
||||
# Copy the https URL, e.g., https://abc123.ngrok.io
|
||||
# Set messaging endpoint to: https://abc123.ngrok.io/api/messages
|
||||
```
|
||||
|
||||
**Option B: Tailscale Funnel**
|
||||
```bash
|
||||
tailscale funnel 3978
|
||||
# Use your Tailscale funnel URL as the messaging endpoint
|
||||
```
|
||||
|
||||
## Teams Developer Portal (Alternative)
|
||||
|
||||
Instead of manually creating a manifest ZIP, you can use the [Teams Developer Portal](https://dev.teams.microsoft.com/apps):
|
||||
|
||||
1. Click **+ New app**
|
||||
2. Fill in basic info (name, description, developer info)
|
||||
3. Go to **App features** → **Bot**
|
||||
4. Select **Enter a bot ID manually** and paste your Azure Bot App ID
|
||||
5. Check scopes: **Personal**, **Team**, **Group Chat**
|
||||
6. Click **Distribute** → **Download app package**
|
||||
7. In Teams: **Apps** → **Manage your apps** → **Upload a custom app** → select the ZIP
|
||||
|
||||
This is often easier than hand-editing JSON manifests.
|
||||
|
||||
## Testing the Bot
|
||||
|
||||
**Option A: Azure Web Chat (verify webhook first)**
|
||||
1. In Azure Portal → your Azure Bot resource → **Test in Web Chat**
|
||||
2. Send a message - you should see a response
|
||||
3. This confirms your webhook endpoint works before Teams setup
|
||||
|
||||
**Option B: Teams (after app installation)**
|
||||
1. Install the Teams app (sideload or org catalog)
|
||||
2. Find the bot in Teams and send a DM
|
||||
3. Check gateway logs for incoming activity
|
||||
|
||||
## Setup (minimal text-only)
|
||||
1. **Install the Microsoft Teams plugin**
|
||||
- From npm: `bot plugins install @bot/msteams`
|
||||
- From a local checkout: `bot plugins install ./extensions/msteams`
|
||||
|
||||
2. **Bot registration**
|
||||
- Create an Azure Bot (see above) and note:
|
||||
- App ID
|
||||
- Client secret (App password)
|
||||
- Tenant ID (single-tenant)
|
||||
|
||||
3. **Teams app manifest**
|
||||
- Include a `bot` entry with `botId = <App ID>`.
|
||||
- Scopes: `personal`, `team`, `groupChat`.
|
||||
- `supportsFiles: true` (required for personal scope file handling).
|
||||
- Add RSC permissions (below).
|
||||
- Create icons: `outline.png` (32x32) and `color.png` (192x192).
|
||||
- Zip all three files together: `manifest.json`, `outline.png`, `color.png`.
|
||||
|
||||
4. **Configure Bot**
|
||||
```json
|
||||
{
|
||||
"msteams": {
|
||||
"enabled": true,
|
||||
"appId": "<APP_ID>",
|
||||
"appPassword": "<APP_PASSWORD>",
|
||||
"tenantId": "<TENANT_ID>",
|
||||
"webhook": { "port": 3978, "path": "/api/messages" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also use environment variables instead of config keys:
|
||||
- `MSTEAMS_APP_ID`
|
||||
- `MSTEAMS_APP_PASSWORD`
|
||||
- `MSTEAMS_TENANT_ID`
|
||||
|
||||
5. **Bot endpoint**
|
||||
- Set the Azure Bot Messaging Endpoint to:
|
||||
- `https://<host>:3978/api/messages` (or your chosen path/port).
|
||||
|
||||
6. **Run the gateway**
|
||||
- The Teams channel starts automatically when the plugin is installed and `msteams` config exists with credentials.
|
||||
|
||||
## History context
|
||||
- `channels.msteams.historyLimit` controls how many recent channel/group messages are wrapped into the prompt.
|
||||
- Falls back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||
- DM history can be limited with `channels.msteams.dmHistoryLimit` (user turns). Per-user overrides: `channels.msteams.dms["<user_id>"].historyLimit`.
|
||||
|
||||
## Current Teams RSC Permissions (Manifest)
|
||||
These are the **existing resourceSpecific permissions** in our Teams app manifest. They only apply inside the team/chat where the app is installed.
|
||||
|
||||
**For channels (team scope):**
|
||||
- `ChannelMessage.Read.Group` (Application) - receive all channel messages without @mention
|
||||
- `ChannelMessage.Send.Group` (Application)
|
||||
- `Member.Read.Group` (Application)
|
||||
- `Owner.Read.Group` (Application)
|
||||
- `ChannelSettings.Read.Group` (Application)
|
||||
- `TeamMember.Read.Group` (Application)
|
||||
- `TeamSettings.Read.Group` (Application)
|
||||
|
||||
**For group chats:**
|
||||
- `ChatMessage.Read.Chat` (Application) - receive all group chat messages without @mention
|
||||
|
||||
## Example Teams Manifest (redacted)
|
||||
Minimal, valid example with the required fields. Replace IDs and URLs.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.23/MicrosoftTeams.schema.json",
|
||||
"manifestVersion": "1.23",
|
||||
"version": "1.0.0",
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"name": { "short": "Bot" },
|
||||
"developer": {
|
||||
"name": "Your Org",
|
||||
"websiteUrl": "https://example.com",
|
||||
"privacyUrl": "https://example.com/privacy",
|
||||
"termsOfUseUrl": "https://example.com/terms"
|
||||
},
|
||||
"description": { "short": "Bot in Teams", "full": "Bot in Teams" },
|
||||
"icons": { "outline": "outline.png", "color": "color.png" },
|
||||
"accentColor": "#5B6DEF",
|
||||
"bots": [
|
||||
{
|
||||
"botId": "11111111-1111-1111-1111-111111111111",
|
||||
"scopes": ["personal", "team", "groupChat"],
|
||||
"isNotificationOnly": false,
|
||||
"supportsCalling": false,
|
||||
"supportsVideo": false,
|
||||
"supportsFiles": true
|
||||
}
|
||||
],
|
||||
"webApplicationInfo": {
|
||||
"id": "11111111-1111-1111-1111-111111111111"
|
||||
},
|
||||
"authorization": {
|
||||
"permissions": {
|
||||
"resourceSpecific": [
|
||||
{ "name": "ChannelMessage.Read.Group", "type": "Application" },
|
||||
{ "name": "ChannelMessage.Send.Group", "type": "Application" },
|
||||
{ "name": "Member.Read.Group", "type": "Application" },
|
||||
{ "name": "Owner.Read.Group", "type": "Application" },
|
||||
{ "name": "ChannelSettings.Read.Group", "type": "Application" },
|
||||
{ "name": "TeamMember.Read.Group", "type": "Application" },
|
||||
{ "name": "TeamSettings.Read.Group", "type": "Application" },
|
||||
{ "name": "ChatMessage.Read.Chat", "type": "Application" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Manifest caveats (must-have fields)
|
||||
- `bots[].botId` **must** match the Azure Bot App ID.
|
||||
- `webApplicationInfo.id` **must** match the Azure Bot App ID.
|
||||
- `bots[].scopes` must include the surfaces you plan to use (`personal`, `team`, `groupChat`).
|
||||
- `bots[].supportsFiles: true` is required for file handling in personal scope.
|
||||
- `authorization.permissions.resourceSpecific` must include channel read/send if you want channel traffic.
|
||||
|
||||
### Updating an existing app
|
||||
|
||||
To update an already-installed Teams app (e.g., to add RSC permissions):
|
||||
|
||||
1. Update your `manifest.json` with the new settings
|
||||
2. **Increment the `version` field** (e.g., `1.0.0` → `1.1.0`)
|
||||
3. **Re-zip** the manifest with icons (`manifest.json`, `outline.png`, `color.png`)
|
||||
4. Upload the new zip:
|
||||
- **Option A (Teams Admin Center):** Teams Admin Center → Teams apps → Manage apps → find your app → Upload new version
|
||||
- **Option B (Sideload):** In Teams → Apps → Manage your apps → Upload a custom app
|
||||
5. **For team channels:** Reinstall the app in each team for new permissions to take effect
|
||||
6. **Fully quit and relaunch Teams** (not just close the window) to clear cached app metadata
|
||||
|
||||
## Capabilities: RSC only vs Graph
|
||||
|
||||
### With **Teams RSC only** (app installed, no Graph API permissions)
|
||||
Works:
|
||||
- Read channel message **text** content.
|
||||
- Send channel message **text** content.
|
||||
- Receive **personal (DM)** file attachments.
|
||||
|
||||
Does NOT work:
|
||||
- Channel/group **image or file contents** (payload only includes HTML stub).
|
||||
- Downloading attachments stored in SharePoint/OneDrive.
|
||||
- Reading message history (beyond the live webhook event).
|
||||
|
||||
### With **Teams RSC + Microsoft Graph Application permissions**
|
||||
Adds:
|
||||
- Downloading hosted contents (images pasted into messages).
|
||||
- Downloading file attachments stored in SharePoint/OneDrive.
|
||||
- Reading channel/chat message history via Graph.
|
||||
|
||||
### RSC vs Graph API
|
||||
|
||||
| Capability | RSC Permissions | Graph API |
|
||||
|------------|-----------------|-----------|
|
||||
| **Real-time messages** | Yes (via webhook) | No (polling only) |
|
||||
| **Historical messages** | No | Yes (can query history) |
|
||||
| **Setup complexity** | App manifest only | Requires admin consent + token flow |
|
||||
| **Works offline** | No (must be running) | Yes (query anytime) |
|
||||
|
||||
**Bottom line:** RSC is for real-time listening; Graph API is for historical access. For catching up on missed messages while offline, you need Graph API with `ChannelMessage.Read.All` (requires admin consent).
|
||||
|
||||
## Graph-enabled media + history (required for channels)
|
||||
If you need images/files in **channels** or want to fetch **message history**, you must enable Microsoft Graph permissions and grant admin consent.
|
||||
|
||||
1. In Entra ID (Azure AD) **App Registration**, add Microsoft Graph **Application permissions**:
|
||||
- `ChannelMessage.Read.All` (channel attachments + history)
|
||||
- `Chat.Read.All` or `ChatMessage.Read.All` (group chats)
|
||||
2. **Grant admin consent** for the tenant.
|
||||
3. Bump the Teams app **manifest version**, re-upload, and **reinstall the app in Teams**.
|
||||
4. **Fully quit and relaunch Teams** to clear cached app metadata.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### Webhook timeouts
|
||||
Teams delivers messages via HTTP webhook. If processing takes too long (e.g., slow LLM responses), you may see:
|
||||
- Gateway timeouts
|
||||
- Teams retrying the message (causing duplicates)
|
||||
- Dropped replies
|
||||
|
||||
Bot handles this by returning quickly and sending replies proactively, but very slow responses may still cause issues.
|
||||
|
||||
### Formatting
|
||||
Teams markdown is more limited than Slack or Discord:
|
||||
- Basic formatting works: **bold**, *italic*, `code`, links
|
||||
- Complex markdown (tables, nested lists) may not render correctly
|
||||
- Adaptive Cards are supported for polls and arbitrary card sends (see below)
|
||||
|
||||
## Configuration
|
||||
Key settings (see `/gateway/configuration` for shared channel patterns):
|
||||
|
||||
- `channels.msteams.enabled`: enable/disable the channel.
|
||||
- `channels.msteams.appId`, `channels.msteams.appPassword`, `channels.msteams.tenantId`: bot credentials.
|
||||
- `channels.msteams.webhook.port` (default `3978`)
|
||||
- `channels.msteams.webhook.path` (default `/api/messages`)
|
||||
- `channels.msteams.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing)
|
||||
- `channels.msteams.allowFrom`: allowlist for DMs (AAD object IDs, UPNs, or display names). The wizard resolves names to IDs during setup when Graph access is available.
|
||||
- `channels.msteams.textChunkLimit`: outbound text chunk size.
|
||||
- `channels.msteams.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.msteams.mediaAllowHosts`: allowlist for inbound attachment hosts (defaults to Microsoft/Teams domains).
|
||||
- `channels.msteams.requireMention`: require @mention in channels/groups (default true).
|
||||
- `channels.msteams.replyStyle`: `thread | top-level` (see [Reply Style](#reply-style-threads-vs-posts)).
|
||||
- `channels.msteams.teams.<teamId>.replyStyle`: per-team override.
|
||||
- `channels.msteams.teams.<teamId>.requireMention`: per-team override.
|
||||
- `channels.msteams.teams.<teamId>.channels.<conversationId>.replyStyle`: per-channel override.
|
||||
- `channels.msteams.teams.<teamId>.channels.<conversationId>.requireMention`: per-channel override.
|
||||
- `channels.msteams.sharePointSiteId`: SharePoint site ID for file uploads in group chats/channels (see [Sending files in group chats](#sending-files-in-group-chats)).
|
||||
|
||||
## Routing & Sessions
|
||||
- Session keys follow the standard agent format (see [/concepts/session](/concepts/session)):
|
||||
- Direct messages share the main session (`agent:<agentId>:<mainKey>`).
|
||||
- Channel/group messages use conversation id:
|
||||
- `agent:<agentId>:msteams:channel:<conversationId>`
|
||||
- `agent:<agentId>:msteams:group:<conversationId>`
|
||||
|
||||
## Reply Style: Threads vs Posts
|
||||
|
||||
Teams recently introduced two channel UI styles over the same underlying data model:
|
||||
|
||||
| Style | Description | Recommended `replyStyle` |
|
||||
|-------|-------------|--------------------------|
|
||||
| **Posts** (classic) | Messages appear as cards with threaded replies underneath | `thread` (default) |
|
||||
| **Threads** (Slack-like) | Messages flow linearly, more like Slack | `top-level` |
|
||||
|
||||
**The problem:** The Teams API does not expose which UI style a channel uses. If you use the wrong `replyStyle`:
|
||||
- `thread` in a Threads-style channel → replies appear nested awkwardly
|
||||
- `top-level` in a Posts-style channel → replies appear as separate top-level posts instead of in-thread
|
||||
|
||||
**Solution:** Configure `replyStyle` per-channel based on how the channel is set up:
|
||||
|
||||
```json
|
||||
{
|
||||
"msteams": {
|
||||
"replyStyle": "thread",
|
||||
"teams": {
|
||||
"19:[email protected]": {
|
||||
"channels": {
|
||||
"19:[email protected]": {
|
||||
"replyStyle": "top-level"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Attachments & Images
|
||||
|
||||
**Current limitations:**
|
||||
- **DMs:** Images and file attachments work via Teams bot file APIs.
|
||||
- **Channels/groups:** Attachments live in M365 storage (SharePoint/OneDrive). The webhook payload only includes an HTML stub, not the actual file bytes. **Graph API permissions are required** to download channel attachments.
|
||||
|
||||
Without Graph permissions, channel messages with images will be received as text-only (the image content is not accessible to the bot).
|
||||
By default, Bot only downloads media from Microsoft/Teams hostnames. Override with `channels.msteams.mediaAllowHosts` (use `["*"]` to allow any host).
|
||||
|
||||
## Sending files in group chats
|
||||
|
||||
Bots can send files in DMs using the FileConsentCard flow (built-in). However, **sending files in group chats/channels** requires additional setup:
|
||||
|
||||
| Context | How files are sent | Setup needed |
|
||||
|---------|-------------------|--------------|
|
||||
| **DMs** | FileConsentCard → user accepts → bot uploads | Works out of the box |
|
||||
| **Group chats/channels** | Upload to SharePoint → share link | Requires `sharePointSiteId` + Graph permissions |
|
||||
| **Images (any context)** | Base64-encoded inline | Works out of the box |
|
||||
|
||||
### Why group chats need SharePoint
|
||||
|
||||
Bots don't have a personal OneDrive drive (the `/me/drive` Graph API endpoint doesn't work for application identities). To send files in group chats/channels, the bot uploads to a **SharePoint site** and creates a sharing link.
|
||||
|
||||
### Setup
|
||||
|
||||
1. **Add Graph API permissions** in Entra ID (Azure AD) → App Registration:
|
||||
- `Sites.ReadWrite.All` (Application) - upload files to SharePoint
|
||||
- `Chat.Read.All` (Application) - optional, enables per-user sharing links
|
||||
|
||||
2. **Grant admin consent** for the tenant.
|
||||
|
||||
3. **Get your SharePoint site ID:**
|
||||
```bash
|
||||
# Via Graph Explorer or curl with a valid token:
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
"https://graph.microsoft.com/v1.0/sites/{hostname}:/{site-path}"
|
||||
|
||||
# Example: for a site at "contoso.sharepoint.com/sites/BotFiles"
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
"https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/BotFiles"
|
||||
|
||||
# Response includes: "id": "contoso.sharepoint.com,guid1,guid2"
|
||||
```
|
||||
|
||||
4. **Configure Bot:**
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
// ... other config ...
|
||||
sharePointSiteId: "contoso.sharepoint.com,guid1,guid2"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sharing behavior
|
||||
|
||||
| Permission | Sharing behavior |
|
||||
|------------|------------------|
|
||||
| `Sites.ReadWrite.All` only | Organization-wide sharing link (anyone in org can access) |
|
||||
| `Sites.ReadWrite.All` + `Chat.Read.All` | Per-user sharing link (only chat members can access) |
|
||||
|
||||
Per-user sharing is more secure as only the chat participants can access the file. If `Chat.Read.All` permission is missing, the bot falls back to organization-wide sharing.
|
||||
|
||||
### Fallback behavior
|
||||
|
||||
| Scenario | Result |
|
||||
|----------|--------|
|
||||
| Group chat + file + `sharePointSiteId` configured | Upload to SharePoint, send sharing link |
|
||||
| Group chat + file + no `sharePointSiteId` | Attempt OneDrive upload (may fail), send text only |
|
||||
| Personal chat + file | FileConsentCard flow (works without SharePoint) |
|
||||
| Any context + image | Base64-encoded inline (works without SharePoint) |
|
||||
|
||||
### Files stored location
|
||||
|
||||
Uploaded files are stored in a `/BotShared/` folder in the configured SharePoint site's default document library.
|
||||
|
||||
## Polls (Adaptive Cards)
|
||||
Bot sends Teams polls as Adaptive Cards (there is no native Teams poll API).
|
||||
|
||||
- CLI: `bot message poll --channel msteams --target conversation:<id> ...`
|
||||
- Votes are recorded by the gateway in `~/.bot/msteams-polls.json`.
|
||||
- The gateway must stay online to record votes.
|
||||
- Polls do not auto-post result summaries yet (inspect the store file if needed).
|
||||
|
||||
## Adaptive Cards (arbitrary)
|
||||
Send any Adaptive Card JSON to Teams users or conversations using the `message` tool or CLI.
|
||||
|
||||
The `card` parameter accepts an Adaptive Card JSON object. When `card` is provided, the message text is optional.
|
||||
|
||||
**Agent tool:**
|
||||
```json
|
||||
{
|
||||
"action": "send",
|
||||
"channel": "msteams",
|
||||
"target": "user:<id>",
|
||||
"card": {
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.5",
|
||||
"body": [{"type": "TextBlock", "text": "Hello!"}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**CLI:**
|
||||
```bash
|
||||
bot message send --channel msteams \
|
||||
--target "conversation:19:[email protected]" \
|
||||
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello!"}]}'
|
||||
```
|
||||
|
||||
See [Adaptive Cards documentation](https://adaptivecards.io/) for card schema and examples. For target format details, see [Target formats](#target-formats) below.
|
||||
|
||||
## Target formats
|
||||
|
||||
MSTeams targets use prefixes to distinguish between users and conversations:
|
||||
|
||||
| Target type | Format | Example |
|
||||
|-------------|--------|---------|
|
||||
| User (by ID) | `user:<aad-object-id>` | `user:40a1a0ed-4ff2-4164-a219-55518990c197` |
|
||||
| User (by name) | `user:<display-name>` | `user:John Smith` (requires Graph API) |
|
||||
| Group/channel | `conversation:<conversation-id>` | `conversation:19:[email protected]` |
|
||||
| Group/channel (raw) | `<conversation-id>` | `19:[email protected]` (if contains `@thread`) |
|
||||
|
||||
**CLI examples:**
|
||||
```bash
|
||||
# Send to a user by ID
|
||||
bot message send --channel msteams --target "user:40a1a0ed-..." --message "Hello"
|
||||
|
||||
# Send to a user by display name (triggers Graph API lookup)
|
||||
bot message send --channel msteams --target "user:John Smith" --message "Hello"
|
||||
|
||||
# Send to a group chat or channel
|
||||
bot message send --channel msteams --target "conversation:19:[email protected]" --message "Hello"
|
||||
|
||||
# Send an Adaptive Card to a conversation
|
||||
bot message send --channel msteams --target "conversation:19:[email protected]" \
|
||||
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello"}]}'
|
||||
```
|
||||
|
||||
**Agent tool examples:**
|
||||
```json
|
||||
{
|
||||
"action": "send",
|
||||
"channel": "msteams",
|
||||
"target": "user:John Smith",
|
||||
"message": "Hello!"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "send",
|
||||
"channel": "msteams",
|
||||
"target": "conversation:19:[email protected]",
|
||||
"card": {"type": "AdaptiveCard", "version": "1.5", "body": [{"type": "TextBlock", "text": "Hello"}]}
|
||||
}
|
||||
```
|
||||
|
||||
Note: Without the `user:` prefix, names default to group/team resolution. Always use `user:` when targeting people by display name.
|
||||
|
||||
## Proactive messaging
|
||||
- Proactive messages are only possible **after** a user has interacted, because we store conversation references at that point.
|
||||
- See `/gateway/configuration` for `dmPolicy` and allowlist gating.
|
||||
|
||||
## Team and Channel IDs (Common Gotcha)
|
||||
|
||||
The `groupId` query parameter in Teams URLs is **NOT** the team ID used for configuration. Extract IDs from the URL path instead:
|
||||
|
||||
**Team URL:**
|
||||
```
|
||||
https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=...
|
||||
└────────────────────────────┘
|
||||
Team ID (URL-decode this)
|
||||
```
|
||||
|
||||
**Channel URL:**
|
||||
```
|
||||
https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=...
|
||||
└─────────────────────────┘
|
||||
Channel ID (URL-decode this)
|
||||
```
|
||||
|
||||
**For config:**
|
||||
- Team ID = path segment after `/team/` (URL-decoded, e.g., `19:[email protected]`)
|
||||
- Channel ID = path segment after `/channel/` (URL-decoded)
|
||||
- **Ignore** the `groupId` query parameter
|
||||
|
||||
## Private Channels
|
||||
|
||||
Bots have limited support in private channels:
|
||||
|
||||
| Feature | Standard Channels | Private Channels |
|
||||
|---------|-------------------|------------------|
|
||||
| Bot installation | Yes | Limited |
|
||||
| Real-time messages (webhook) | Yes | May not work |
|
||||
| RSC permissions | Yes | May behave differently |
|
||||
| @mentions | Yes | If bot is accessible |
|
||||
| Graph API history | Yes | Yes (with permissions) |
|
||||
|
||||
**Workarounds if private channels don't work:**
|
||||
1. Use standard channels for bot interactions
|
||||
2. Use DMs - users can always message the bot directly
|
||||
3. Use Graph API for historical access (requires `ChannelMessage.Read.All`)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common issues
|
||||
|
||||
- **Images not showing in channels:** Graph permissions or admin consent missing. Reinstall the Teams app and fully quit/reopen Teams.
|
||||
- **No responses in channel:** mentions are required by default; set `channels.msteams.requireMention=false` or configure per team/channel.
|
||||
- **Version mismatch (Teams still shows old manifest):** remove + re-add the app and fully quit Teams to refresh.
|
||||
- **401 Unauthorized from webhook:** Expected when testing manually without Azure JWT - means endpoint is reachable but auth failed. Use Azure Web Chat to test properly.
|
||||
|
||||
### Manifest upload errors
|
||||
|
||||
- **"Icon file cannot be empty":** The manifest references icon files that are 0 bytes. Create valid PNG icons (32x32 for `outline.png`, 192x192 for `color.png`).
|
||||
- **"webApplicationInfo.Id already in use":** The app is still installed in another team/chat. Find and uninstall it first, or wait 5-10 minutes for propagation.
|
||||
- **"Something went wrong" on upload:** Upload via https://admin.teams.microsoft.com instead, open browser DevTools (F12) → Network tab, and check the response body for the actual error.
|
||||
- **Sideload failing:** Try "Upload an app to your org's app catalog" instead of "Upload a custom app" - this often bypasses sideload restrictions.
|
||||
|
||||
### RSC permissions not working
|
||||
|
||||
1. Verify `webApplicationInfo.id` matches your bot's App ID exactly
|
||||
2. Re-upload the app and reinstall in the team/chat
|
||||
3. Check if your org admin has blocked RSC permissions
|
||||
4. Confirm you're using the right scope: `ChannelMessage.Read.Group` for teams, `ChatMessage.Read.Chat` for group chats
|
||||
|
||||
## References
|
||||
- [Create Azure Bot](https://learn.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) - Azure Bot setup guide
|
||||
- [Teams Developer Portal](https://dev.teams.microsoft.com/apps) - create/manage Teams apps
|
||||
- [Teams app manifest schema](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema)
|
||||
- [Receive channel messages with RSC](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/channel-messages-with-rsc)
|
||||
- [RSC permissions reference](https://learn.microsoft.com/en-us/microsoftteams/platform/graph-api/rsc/resource-specific-consent)
|
||||
- [Teams bot file handling](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/bots-filesv4) (channel/group requires Graph)
|
||||
- [Proactive messaging](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/send-proactive-messages)
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
title: "Nextcloud Talk (plugin)"
|
||||
description: "Nextcloud Talk support status, capabilities, and configuration"
|
||||
---
|
||||
|
||||
Status: supported via plugin (webhook bot). Direct messages, rooms, reactions, and markdown messages are supported.
|
||||
|
||||
## Plugin required
|
||||
Nextcloud Talk ships as a plugin and is not bundled with the core install.
|
||||
|
||||
Install via CLI (npm registry):
|
||||
```bash
|
||||
bot plugins install @bot/nextcloud-talk
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
```bash
|
||||
bot plugins install ./extensions/nextcloud-talk
|
||||
```
|
||||
|
||||
If you choose Nextcloud Talk during configure/onboarding and a git checkout is detected,
|
||||
Bot will offer the local install path automatically.
|
||||
|
||||
Details: [Plugins](/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Install the Nextcloud Talk plugin.
|
||||
2) On your Nextcloud server, create a bot:
|
||||
```bash
|
||||
./occ talk:bot:install "Bot" "<shared-secret>" "<webhook-url>" --feature reaction
|
||||
```
|
||||
3) Enable the bot in the target room settings.
|
||||
4) Configure Bot:
|
||||
- Config: `channels.nextcloud-talk.baseUrl` + `channels.nextcloud-talk.botSecret`
|
||||
- Or env: `NEXTCLOUD_TALK_BOT_SECRET` (default account only)
|
||||
5) Restart the gateway (or finish onboarding).
|
||||
|
||||
Minimal config:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
"nextcloud-talk": {
|
||||
enabled: true,
|
||||
baseUrl: "https://cloud.example.com",
|
||||
botSecret: "shared-secret",
|
||||
dmPolicy: "pairing"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Bots cannot initiate DMs. The user must message the bot first.
|
||||
- Webhook URL must be reachable by the Gateway; set `webhookPublicUrl` if behind a proxy.
|
||||
- Media uploads are not supported by the bot API; media is sent as URLs.
|
||||
- The webhook payload does not distinguish DMs vs rooms; set `apiUser` + `apiPassword` to enable room-type lookups (otherwise DMs are treated as rooms).
|
||||
|
||||
## Access control (DMs)
|
||||
- Default: `channels.nextcloud-talk.dmPolicy = "pairing"`. Unknown senders get a pairing code.
|
||||
- Approve via:
|
||||
- `bot pairing list nextcloud-talk`
|
||||
- `bot pairing approve nextcloud-talk <CODE>`
|
||||
- Public DMs: `channels.nextcloud-talk.dmPolicy="open"` plus `channels.nextcloud-talk.allowFrom=["*"]`.
|
||||
|
||||
## Rooms (groups)
|
||||
- Default: `channels.nextcloud-talk.groupPolicy = "allowlist"` (mention-gated).
|
||||
- Allowlist rooms with `channels.nextcloud-talk.rooms`:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
"nextcloud-talk": {
|
||||
rooms: {
|
||||
"room-token": { requireMention: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
- To allow no rooms, keep the allowlist empty or set `channels.nextcloud-talk.groupPolicy="disabled"`.
|
||||
|
||||
## Capabilities
|
||||
| Feature | Status |
|
||||
|---------|--------|
|
||||
| Direct messages | Supported |
|
||||
| Rooms | Supported |
|
||||
| Threads | Not supported |
|
||||
| Media | URL-only |
|
||||
| Reactions | Supported |
|
||||
| Native commands | Not supported |
|
||||
|
||||
## Configuration reference (Nextcloud Talk)
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
- `channels.nextcloud-talk.enabled`: enable/disable channel startup.
|
||||
- `channels.nextcloud-talk.baseUrl`: Nextcloud instance URL.
|
||||
- `channels.nextcloud-talk.botSecret`: bot shared secret.
|
||||
- `channels.nextcloud-talk.botSecretFile`: secret file path.
|
||||
- `channels.nextcloud-talk.apiUser`: API user for room lookups (DM detection).
|
||||
- `channels.nextcloud-talk.apiPassword`: API/app password for room lookups.
|
||||
- `channels.nextcloud-talk.apiPasswordFile`: API password file path.
|
||||
- `channels.nextcloud-talk.webhookPort`: webhook listener port (default: 8788).
|
||||
- `channels.nextcloud-talk.webhookHost`: webhook host (default: 0.0.0.0).
|
||||
- `channels.nextcloud-talk.webhookPath`: webhook path (default: /nextcloud-talk-webhook).
|
||||
- `channels.nextcloud-talk.webhookPublicUrl`: externally reachable webhook URL.
|
||||
- `channels.nextcloud-talk.dmPolicy`: `pairing | allowlist | open | disabled`.
|
||||
- `channels.nextcloud-talk.allowFrom`: DM allowlist (user IDs). `open` requires `"*"`.
|
||||
- `channels.nextcloud-talk.groupPolicy`: `allowlist | open | disabled`.
|
||||
- `channels.nextcloud-talk.groupAllowFrom`: group allowlist (user IDs).
|
||||
- `channels.nextcloud-talk.rooms`: per-room settings and allowlist.
|
||||
- `channels.nextcloud-talk.historyLimit`: group history limit (0 disables).
|
||||
- `channels.nextcloud-talk.dmHistoryLimit`: DM history limit (0 disables).
|
||||
- `channels.nextcloud-talk.dms`: per-DM overrides (historyLimit).
|
||||
- `channels.nextcloud-talk.textChunkLimit`: outbound text chunk size (chars).
|
||||
- `channels.nextcloud-talk.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.nextcloud-talk.blockStreaming`: disable block streaming for this channel.
|
||||
- `channels.nextcloud-talk.blockStreamingCoalesce`: block streaming coalesce tuning.
|
||||
- `channels.nextcloud-talk.mediaMaxMb`: inbound media cap (MB).
|
||||
@@ -0,0 +1,232 @@
|
||||
---
|
||||
title: "Nostr"
|
||||
description: "Nostr DM channel via NIP-04 encrypted messages"
|
||||
---
|
||||
|
||||
**Status:** Optional plugin (disabled by default).
|
||||
|
||||
Nostr is a decentralized protocol for social networking. This channel enables Bot to receive and respond to encrypted direct messages (DMs) via NIP-04.
|
||||
|
||||
## Install (on demand)
|
||||
|
||||
### Onboarding (recommended)
|
||||
|
||||
- The onboarding wizard (`bot onboard`) and `bot channels add` list optional channel plugins.
|
||||
- Selecting Nostr prompts you to install the plugin on demand.
|
||||
|
||||
Install defaults:
|
||||
|
||||
- **Dev channel + git checkout available:** uses the local plugin path.
|
||||
- **Stable/Beta:** downloads from npm.
|
||||
|
||||
You can always override the choice in the prompt.
|
||||
|
||||
### Manual install
|
||||
|
||||
```bash
|
||||
bot plugins install @bot/nostr
|
||||
```
|
||||
|
||||
Use a local checkout (dev workflows):
|
||||
|
||||
```bash
|
||||
bot plugins install --link <path-to-bot>/extensions/nostr
|
||||
```
|
||||
|
||||
Restart the Gateway after installing or enabling plugins.
|
||||
|
||||
## Quick setup
|
||||
|
||||
1) Generate a Nostr keypair (if needed):
|
||||
|
||||
```bash
|
||||
# Using nak
|
||||
nak key generate
|
||||
```
|
||||
|
||||
2) Add to config:
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"nostr": {
|
||||
"privateKey": "${NOSTR_PRIVATE_KEY}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3) Export the key:
|
||||
|
||||
```bash
|
||||
export NOSTR_PRIVATE_KEY="nsec1..."
|
||||
```
|
||||
|
||||
4) Restart the Gateway.
|
||||
|
||||
## Configuration reference
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `privateKey` | string | required | Private key in `nsec` or hex format |
|
||||
| `relays` | string[] | `['wss://relay.damus.io', 'wss://nos.lol']` | Relay URLs (WebSocket) |
|
||||
| `dmPolicy` | string | `pairing` | DM access policy |
|
||||
| `allowFrom` | string[] | `[]` | Allowed sender pubkeys |
|
||||
| `enabled` | boolean | `true` | Enable/disable channel |
|
||||
| `name` | string | - | Display name |
|
||||
| `profile` | object | - | NIP-01 profile metadata |
|
||||
|
||||
## Profile metadata
|
||||
|
||||
Profile data is published as a NIP-01 `kind:0` event. You can manage it from the Control UI (Channels -> Nostr -> Profile) or set it directly in config.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"nostr": {
|
||||
"privateKey": "${NOSTR_PRIVATE_KEY}",
|
||||
"profile": {
|
||||
"name": "bot",
|
||||
"displayName": "Bot",
|
||||
"about": "Personal assistant DM bot",
|
||||
"picture": "https://example.com/avatar.png",
|
||||
"banner": "https://example.com/banner.png",
|
||||
"website": "https://example.com",
|
||||
"nip05": "[email protected]",
|
||||
"lud16": "[email protected]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Profile URLs must use `https://`.
|
||||
- Importing from relays merges fields and preserves local overrides.
|
||||
|
||||
## Access control
|
||||
|
||||
### DM policies
|
||||
|
||||
- **pairing** (default): unknown senders get a pairing code.
|
||||
- **allowlist**: only pubkeys in `allowFrom` can DM.
|
||||
- **open**: public inbound DMs (requires `allowFrom: ["*"]`).
|
||||
- **disabled**: ignore inbound DMs.
|
||||
|
||||
### Allowlist example
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"nostr": {
|
||||
"privateKey": "${NOSTR_PRIVATE_KEY}",
|
||||
"dmPolicy": "allowlist",
|
||||
"allowFrom": ["npub1abc...", "npub1xyz..."]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Key formats
|
||||
|
||||
Accepted formats:
|
||||
|
||||
- **Private key:** `nsec...` or 64-char hex
|
||||
- **Pubkeys (`allowFrom`):** `npub...` or hex
|
||||
|
||||
## Relays
|
||||
|
||||
Defaults: `relay.damus.io` and `nos.lol`.
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"nostr": {
|
||||
"privateKey": "${NOSTR_PRIVATE_KEY}",
|
||||
"relays": [
|
||||
"wss://relay.damus.io",
|
||||
"wss://relay.primal.net",
|
||||
"wss://nostr.wine"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Tips:
|
||||
|
||||
- Use 2-3 relays for redundancy.
|
||||
- Avoid too many relays (latency, duplication).
|
||||
- Paid relays can improve reliability.
|
||||
- Local relays are fine for testing (`ws://localhost:7777`).
|
||||
|
||||
## Protocol support
|
||||
|
||||
| NIP | Status | Description |
|
||||
| --- | --- | --- |
|
||||
| NIP-01 | Supported | Basic event format + profile metadata |
|
||||
| NIP-04 | Supported | Encrypted DMs (`kind:4`) |
|
||||
| NIP-17 | Planned | Gift-wrapped DMs |
|
||||
| NIP-44 | Planned | Versioned encryption |
|
||||
|
||||
## Testing
|
||||
|
||||
### Local relay
|
||||
|
||||
```bash
|
||||
# Start strfry
|
||||
docker run -p 7777:7777 ghcr.io/hoytech/strfry
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"nostr": {
|
||||
"privateKey": "${NOSTR_PRIVATE_KEY}",
|
||||
"relays": ["ws://localhost:7777"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Manual test
|
||||
|
||||
1) Note the bot pubkey (npub) from logs.
|
||||
2) Open a Nostr client (Damus, Amethyst, etc.).
|
||||
3) DM the bot pubkey.
|
||||
4) Verify the response.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Not receiving messages
|
||||
|
||||
- Verify the private key is valid.
|
||||
- Ensure relay URLs are reachable and use `wss://` (or `ws://` for local).
|
||||
- Confirm `enabled` is not `false`.
|
||||
- Check Gateway logs for relay connection errors.
|
||||
|
||||
### Not sending responses
|
||||
|
||||
- Check relay accepts writes.
|
||||
- Verify outbound connectivity.
|
||||
- Watch for relay rate limits.
|
||||
|
||||
### Duplicate responses
|
||||
|
||||
- Expected when using multiple relays.
|
||||
- Messages are deduplicated by event ID; only the first delivery triggers a response.
|
||||
|
||||
## Security
|
||||
|
||||
- Never commit private keys.
|
||||
- Use environment variables for keys.
|
||||
- Consider `allowlist` for production bots.
|
||||
|
||||
## Limitations (MVP)
|
||||
|
||||
- Direct messages only (no group chats).
|
||||
- No media attachments.
|
||||
- NIP-04 only (NIP-17 gift-wrap planned).
|
||||
@@ -0,0 +1,175 @@
|
||||
---
|
||||
title: "Signal (signal-cli)"
|
||||
description: "Signal support via signal-cli (JSON-RPC + SSE), setup, and number model"
|
||||
---
|
||||
|
||||
Status: external CLI integration. Gateway talks to `signal-cli` over HTTP JSON-RPC + SSE.
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Use a **separate Signal number** for the bot (recommended).
|
||||
2) Install `signal-cli` (Java required).
|
||||
3) Link the bot device and start the daemon:
|
||||
- `signal-cli link -n "Bot"`
|
||||
4) Configure Bot and start the gateway.
|
||||
|
||||
Minimal config:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
signal: {
|
||||
enabled: true,
|
||||
account: "+15551234567",
|
||||
cliPath: "signal-cli",
|
||||
dmPolicy: "pairing",
|
||||
allowFrom: ["+15557654321"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## What it is
|
||||
- Signal channel via `signal-cli` (not embedded libsignal).
|
||||
- Deterministic routing: replies always go back to Signal.
|
||||
- DMs share the agent's main session; groups are isolated (`agent:<agentId>:signal:group:<groupId>`).
|
||||
|
||||
## Config writes
|
||||
By default, Signal is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||
|
||||
Disable with:
|
||||
```json5
|
||||
{
|
||||
channels: { signal: { configWrites: false } }
|
||||
}
|
||||
```
|
||||
|
||||
## The number model (important)
|
||||
- The gateway connects to a **Signal device** (the `signal-cli` account).
|
||||
- If you run the bot on **your personal Signal account**, it will ignore your own messages (loop protection).
|
||||
- For "I text the bot and it replies," use a **separate bot number**.
|
||||
|
||||
## Setup (fast path)
|
||||
1) Install `signal-cli` (Java required).
|
||||
2) Link a bot account:
|
||||
- `signal-cli link -n "Bot"` then scan the QR in Signal.
|
||||
3) Configure Signal and start the gateway.
|
||||
|
||||
Example:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
signal: {
|
||||
enabled: true,
|
||||
account: "+15551234567",
|
||||
cliPath: "signal-cli",
|
||||
dmPolicy: "pairing",
|
||||
allowFrom: ["+15557654321"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Multi-account support: use `channels.signal.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
||||
|
||||
## External daemon mode (httpUrl)
|
||||
If you want to manage `signal-cli` yourself (slow JVM cold starts, container init, or shared CPUs), run the daemon separately and point Bot at it:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
signal: {
|
||||
httpUrl: "http://127.0.0.1:8080",
|
||||
autoStart: false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This skips auto-spawn and the startup wait inside Bot. For slow starts when auto-spawning, set `channels.signal.startupTimeoutMs`.
|
||||
|
||||
## Access control (DMs + groups)
|
||||
DMs:
|
||||
- Default: `channels.signal.dmPolicy = "pairing"`.
|
||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
- `bot pairing list signal`
|
||||
- `bot pairing approve signal <CODE>`
|
||||
- Pairing is the default token exchange for Signal DMs. Details: [Pairing](/start/pairing)
|
||||
- UUID-only senders (from `sourceUuid`) are stored as `uuid:<id>` in `channels.signal.allowFrom`.
|
||||
|
||||
Groups:
|
||||
- `channels.signal.groupPolicy = open | allowlist | disabled`.
|
||||
- `channels.signal.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||
|
||||
## How it works (behavior)
|
||||
- `signal-cli` runs as a daemon; the gateway reads events via SSE.
|
||||
- Inbound messages are normalized into the shared channel envelope.
|
||||
- Replies always route back to the same number or group.
|
||||
|
||||
## Media + limits
|
||||
- Outbound text is chunked to `channels.signal.textChunkLimit` (default 4000).
|
||||
- Optional newline chunking: set `channels.signal.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- Attachments supported (base64 fetched from `signal-cli`).
|
||||
- Default media cap: `channels.signal.mediaMaxMb` (default 8).
|
||||
- Use `channels.signal.ignoreAttachments` to skip downloading media.
|
||||
- Group history context uses `channels.signal.historyLimit` (or `channels.signal.accounts.*.historyLimit`), falling back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||
|
||||
## Typing + read receipts
|
||||
- **Typing indicators**: Bot sends typing signals via `signal-cli sendTyping` and refreshes them while a reply is running.
|
||||
- **Read receipts**: when `channels.signal.sendReadReceipts` is true, Bot forwards read receipts for allowed DMs.
|
||||
- Signal-cli does not expose read receipts for groups.
|
||||
|
||||
## Reactions (message tool)
|
||||
- Use `message action=react` with `channel=signal`.
|
||||
- Targets: sender E.164 or UUID (use `uuid:<id>` from pairing output; bare UUID works too).
|
||||
- `messageId` is the Signal timestamp for the message you’re reacting to.
|
||||
- Group reactions require `targetAuthor` or `targetAuthorUuid`.
|
||||
|
||||
Examples:
|
||||
```
|
||||
message action=react channel=signal target=uuid:123e4567-e89b-12d3-a456-426614174000 messageId=1737630212345 emoji=🔥
|
||||
message action=react channel=signal target=+15551234567 messageId=1737630212345 emoji=🔥 remove=true
|
||||
message action=react channel=signal target=signal:group:<groupId> targetAuthor=uuid:<sender-uuid> messageId=1737630212345 emoji=✅
|
||||
```
|
||||
|
||||
Config:
|
||||
- `channels.signal.actions.reactions`: enable/disable reaction actions (default true).
|
||||
- `channels.signal.reactionLevel`: `off | ack | minimal | extensive`.
|
||||
- `off`/`ack` disables agent reactions (message tool `react` will error).
|
||||
- `minimal`/`extensive` enables agent reactions and sets the guidance level.
|
||||
- Per-account overrides: `channels.signal.accounts.<id>.actions.reactions`, `channels.signal.accounts.<id>.reactionLevel`.
|
||||
|
||||
## Delivery targets (CLI/cron)
|
||||
- DMs: `signal:+15551234567` (or plain E.164).
|
||||
- UUID DMs: `uuid:<id>` (or bare UUID).
|
||||
- Groups: `signal:group:<groupId>`.
|
||||
- Usernames: `username:<name>` (if supported by your Signal account).
|
||||
|
||||
## Configuration reference (Signal)
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
- `channels.signal.enabled`: enable/disable channel startup.
|
||||
- `channels.signal.account`: E.164 for the bot account.
|
||||
- `channels.signal.cliPath`: path to `signal-cli`.
|
||||
- `channels.signal.httpUrl`: full daemon URL (overrides host/port).
|
||||
- `channels.signal.httpHost`, `channels.signal.httpPort`: daemon bind (default 127.0.0.1:8080).
|
||||
- `channels.signal.autoStart`: auto-spawn daemon (default true if `httpUrl` unset).
|
||||
- `channels.signal.startupTimeoutMs`: startup wait timeout in ms (cap 120000).
|
||||
- `channels.signal.receiveMode`: `on-start | manual`.
|
||||
- `channels.signal.ignoreAttachments`: skip attachment downloads.
|
||||
- `channels.signal.ignoreStories`: ignore stories from the daemon.
|
||||
- `channels.signal.sendReadReceipts`: forward read receipts.
|
||||
- `channels.signal.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing).
|
||||
- `channels.signal.allowFrom`: DM allowlist (E.164 or `uuid:<id>`). `open` requires `"*"`. Signal has no usernames; use phone/UUID ids.
|
||||
- `channels.signal.groupPolicy`: `open | allowlist | disabled` (default: allowlist).
|
||||
- `channels.signal.groupAllowFrom`: group sender allowlist.
|
||||
- `channels.signal.historyLimit`: max group messages to include as context (0 disables).
|
||||
- `channels.signal.dmHistoryLimit`: DM history limit in user turns. Per-user overrides: `channels.signal.dms["<phone_or_uuid>"].historyLimit`.
|
||||
- `channels.signal.textChunkLimit`: outbound chunk size (chars).
|
||||
- `channels.signal.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.signal.mediaMaxMb`: inbound/outbound media cap (MB).
|
||||
|
||||
Related global options:
|
||||
- `agents.list[].groupChat.mentionPatterns` (Signal does not support native mentions).
|
||||
- `messages.groupChat.mentionPatterns` (global fallback).
|
||||
- `messages.responsePrefix`.
|
||||
@@ -0,0 +1,505 @@
|
||||
---
|
||||
title: "Slack"
|
||||
description: "Slack setup for socket or HTTP webhook mode"
|
||||
---
|
||||
|
||||
## Socket mode (default)
|
||||
|
||||
### Quick setup (beginner)
|
||||
1) Create a Slack app and enable **Socket Mode**.
|
||||
2) Create an **App Token** (`xapp-...`) and **Bot Token** (`xoxb-...`).
|
||||
3) Set tokens for Bot and start the gateway.
|
||||
|
||||
Minimal config:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
enabled: true,
|
||||
appToken: "xapp-...",
|
||||
botToken: "xoxb-..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Setup
|
||||
1) Create a Slack app (From scratch) in https://api.slack.com/apps.
|
||||
2) **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`).
|
||||
3) **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`).
|
||||
4) Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`).
|
||||
5) **Event Subscriptions** → enable events and subscribe to:
|
||||
- `message.*` (includes edits/deletes/thread broadcasts)
|
||||
- `app_mention`
|
||||
- `reaction_added`, `reaction_removed`
|
||||
- `member_joined_channel`, `member_left_channel`
|
||||
- `channel_rename`
|
||||
- `pin_added`, `pin_removed`
|
||||
6) Invite the bot to channels you want it to read.
|
||||
7) Slash Commands → create `/bot` if you use `channels.slack.slashCommand`. If you enable native commands, add one slash command per built-in command (same names as `/help`). Native defaults to off for Slack unless you set `channels.slack.commands.native: true` (global `commands.native` is `"auto"` which leaves Slack off).
|
||||
8) App Home → enable the **Messages Tab** so users can DM the bot.
|
||||
|
||||
Use the manifest below so scopes and events stay in sync.
|
||||
|
||||
Multi-account support: use `channels.slack.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
||||
|
||||
### Bot config (minimal)
|
||||
|
||||
Set tokens via env vars (recommended):
|
||||
- `SLACK_APP_TOKEN=xapp-...`
|
||||
- `SLACK_BOT_TOKEN=xoxb-...`
|
||||
|
||||
Or via config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
enabled: true,
|
||||
appToken: "xapp-...",
|
||||
botToken: "xoxb-..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### User token (optional)
|
||||
Bot can use a Slack user token (`xoxp-...`) for read operations (history,
|
||||
pins, reactions, emoji, member info). By default this stays read-only: reads
|
||||
prefer the user token when present, and writes still use the bot token unless
|
||||
you explicitly opt in. Even with `userTokenReadOnly: false`, the bot token stays
|
||||
preferred for writes when it is available.
|
||||
|
||||
User tokens are configured in the config file (no env var support). For
|
||||
multi-account, set `channels.slack.accounts.<id>.userToken`.
|
||||
|
||||
Example with bot + app + user tokens:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
enabled: true,
|
||||
appToken: "xapp-...",
|
||||
botToken: "xoxb-...",
|
||||
userToken: "xoxp-..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example with userTokenReadOnly explicitly set (allow user token writes):
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
enabled: true,
|
||||
appToken: "xapp-...",
|
||||
botToken: "xoxb-...",
|
||||
userToken: "xoxp-...",
|
||||
userTokenReadOnly: false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Token usage
|
||||
- Read operations (history, reactions list, pins list, emoji list, member info,
|
||||
search) prefer the user token when configured, otherwise the bot token.
|
||||
- Write operations (send/edit/delete messages, add/remove reactions, pin/unpin,
|
||||
file uploads) use the bot token by default. If `userTokenReadOnly: false` and
|
||||
no bot token is available, Bot falls back to the user token.
|
||||
|
||||
### History context
|
||||
- `channels.slack.historyLimit` (or `channels.slack.accounts.*.historyLimit`) controls how many recent channel/group messages are wrapped into the prompt.
|
||||
- Falls back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||
|
||||
## HTTP mode (Events API)
|
||||
Use HTTP webhook mode when your Gateway is reachable by Slack over HTTPS (typical for server deployments).
|
||||
HTTP mode uses the Events API + Interactivity + Slash Commands with a shared request URL.
|
||||
|
||||
### Setup
|
||||
1) Create a Slack app and **disable Socket Mode** (optional if you only use HTTP).
|
||||
2) **Basic Information** → copy the **Signing Secret**.
|
||||
3) **OAuth & Permissions** → install the app and copy the **Bot User OAuth Token** (`xoxb-...`).
|
||||
4) **Event Subscriptions** → enable events and set the **Request URL** to your gateway webhook path (default `/slack/events`).
|
||||
5) **Interactivity & Shortcuts** → enable and set the same **Request URL**.
|
||||
6) **Slash Commands** → set the same **Request URL** for your command(s).
|
||||
|
||||
Example request URL:
|
||||
`https://gateway-host/slack/events`
|
||||
|
||||
### Bot config (minimal)
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
enabled: true,
|
||||
mode: "http",
|
||||
botToken: "xoxb-...",
|
||||
signingSecret: "your-signing-secret",
|
||||
webhookPath: "/slack/events"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Multi-account HTTP mode: set `channels.slack.accounts.<id>.mode = "http"` and provide a unique
|
||||
`webhookPath` per account so each Slack app can point to its own URL.
|
||||
|
||||
### Manifest (optional)
|
||||
Use this Slack app manifest to create the app quickly (adjust the name/command if you want). Include the
|
||||
user scopes if you plan to configure a user token.
|
||||
|
||||
```json
|
||||
{
|
||||
"display_information": {
|
||||
"name": "Bot",
|
||||
"description": "Slack connector for Bot"
|
||||
},
|
||||
"features": {
|
||||
"bot_user": {
|
||||
"display_name": "Bot",
|
||||
"always_online": false
|
||||
},
|
||||
"app_home": {
|
||||
"messages_tab_enabled": true,
|
||||
"messages_tab_read_only_enabled": false
|
||||
},
|
||||
"slash_commands": [
|
||||
{
|
||||
"command": "/bot",
|
||||
"description": "Send a message to Bot",
|
||||
"should_escape": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"oauth_config": {
|
||||
"scopes": {
|
||||
"bot": [
|
||||
"chat:write",
|
||||
"channels:history",
|
||||
"channels:read",
|
||||
"groups:history",
|
||||
"groups:read",
|
||||
"groups:write",
|
||||
"im:history",
|
||||
"im:read",
|
||||
"im:write",
|
||||
"mpim:history",
|
||||
"mpim:read",
|
||||
"mpim:write",
|
||||
"users:read",
|
||||
"app_mentions:read",
|
||||
"reactions:read",
|
||||
"reactions:write",
|
||||
"pins:read",
|
||||
"pins:write",
|
||||
"emoji:read",
|
||||
"commands",
|
||||
"files:read",
|
||||
"files:write"
|
||||
],
|
||||
"user": [
|
||||
"channels:history",
|
||||
"channels:read",
|
||||
"groups:history",
|
||||
"groups:read",
|
||||
"im:history",
|
||||
"im:read",
|
||||
"mpim:history",
|
||||
"mpim:read",
|
||||
"users:read",
|
||||
"reactions:read",
|
||||
"pins:read",
|
||||
"emoji:read",
|
||||
"search:read"
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"socket_mode_enabled": true,
|
||||
"event_subscriptions": {
|
||||
"bot_events": [
|
||||
"app_mention",
|
||||
"message.channels",
|
||||
"message.groups",
|
||||
"message.im",
|
||||
"message.mpim",
|
||||
"reaction_added",
|
||||
"reaction_removed",
|
||||
"member_joined_channel",
|
||||
"member_left_channel",
|
||||
"channel_rename",
|
||||
"pin_added",
|
||||
"pin_removed"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you enable native commands, add one `slash_commands` entry per command you want to expose (matching the `/help` list). Override with `channels.slack.commands.native`.
|
||||
|
||||
## Scopes (current vs optional)
|
||||
Slack's Conversations API is type-scoped: you only need the scopes for the
|
||||
conversation types you actually touch (channels, groups, im, mpim). See
|
||||
https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overview.
|
||||
|
||||
### Bot token scopes (required)
|
||||
- `chat:write` (send/update/delete messages via `chat.postMessage`)
|
||||
https://docs.slack.dev/reference/methods/chat.postMessage
|
||||
- `im:write` (open DMs via `conversations.open` for user DMs)
|
||||
https://docs.slack.dev/reference/methods/conversations.open
|
||||
- `channels:history`, `groups:history`, `im:history`, `mpim:history`
|
||||
https://docs.slack.dev/reference/methods/conversations.history
|
||||
- `channels:read`, `groups:read`, `im:read`, `mpim:read`
|
||||
https://docs.slack.dev/reference/methods/conversations.info
|
||||
- `users:read` (user lookup)
|
||||
https://docs.slack.dev/reference/methods/users.info
|
||||
- `reactions:read`, `reactions:write` (`reactions.get` / `reactions.add`)
|
||||
https://docs.slack.dev/reference/methods/reactions.get
|
||||
https://docs.slack.dev/reference/methods/reactions.add
|
||||
- `pins:read`, `pins:write` (`pins.list` / `pins.add` / `pins.remove`)
|
||||
https://docs.slack.dev/reference/scopes/pins.read
|
||||
https://docs.slack.dev/reference/scopes/pins.write
|
||||
- `emoji:read` (`emoji.list`)
|
||||
https://docs.slack.dev/reference/scopes/emoji.read
|
||||
- `files:write` (uploads via `files.uploadV2`)
|
||||
https://docs.slack.dev/messaging/working-with-files/#upload
|
||||
|
||||
### User token scopes (optional, read-only by default)
|
||||
Add these under **User Token Scopes** if you configure `channels.slack.userToken`.
|
||||
|
||||
- `channels:history`, `groups:history`, `im:history`, `mpim:history`
|
||||
- `channels:read`, `groups:read`, `im:read`, `mpim:read`
|
||||
- `users:read`
|
||||
- `reactions:read`
|
||||
- `pins:read`
|
||||
- `emoji:read`
|
||||
- `search:read`
|
||||
|
||||
### Not needed today (but likely future)
|
||||
- `mpim:write` (only if we add group-DM open/DM start via `conversations.open`)
|
||||
- `groups:write` (only if we add private-channel management: create/rename/invite/archive)
|
||||
- `chat:write.public` (only if we want to post to channels the bot isn't in)
|
||||
https://docs.slack.dev/reference/scopes/chat.write.public
|
||||
- `users:read.email` (only if we need email fields from `users.info`)
|
||||
https://docs.slack.dev/changelog/2017-04-narrowing-email-access
|
||||
- `files:read` (only if we start listing/reading file metadata)
|
||||
|
||||
## Config
|
||||
Slack uses Socket Mode only (no HTTP webhook server). Provide both tokens:
|
||||
|
||||
```json
|
||||
{
|
||||
"slack": {
|
||||
"enabled": true,
|
||||
"botToken": "xoxb-...",
|
||||
"appToken": "xapp-...",
|
||||
"groupPolicy": "allowlist",
|
||||
"dm": {
|
||||
"enabled": true,
|
||||
"policy": "pairing",
|
||||
"allowFrom": ["U123", "U456", "*"],
|
||||
"groupEnabled": false,
|
||||
"groupChannels": ["G123"],
|
||||
"replyToMode": "all"
|
||||
},
|
||||
"channels": {
|
||||
"C123": { "allow": true, "requireMention": true },
|
||||
"#general": {
|
||||
"allow": true,
|
||||
"requireMention": true,
|
||||
"users": ["U123"],
|
||||
"skills": ["search", "docs"],
|
||||
"systemPrompt": "Keep answers short."
|
||||
}
|
||||
},
|
||||
"reactionNotifications": "own",
|
||||
"reactionAllowlist": ["U123"],
|
||||
"replyToMode": "off",
|
||||
"actions": {
|
||||
"reactions": true,
|
||||
"messages": true,
|
||||
"pins": true,
|
||||
"memberInfo": true,
|
||||
"emojiList": true
|
||||
},
|
||||
"slashCommand": {
|
||||
"enabled": true,
|
||||
"name": "bot",
|
||||
"sessionPrefix": "slack:slash",
|
||||
"ephemeral": true
|
||||
},
|
||||
"textChunkLimit": 4000,
|
||||
"mediaMaxMb": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Tokens can also be supplied via env vars:
|
||||
- `SLACK_BOT_TOKEN`
|
||||
- `SLACK_APP_TOKEN`
|
||||
|
||||
Ack reactions are controlled globally via `messages.ackReaction` +
|
||||
`messages.ackReactionScope`. Use `messages.removeAckAfterReply` to clear the
|
||||
ack reaction after the bot replies.
|
||||
|
||||
## Limits
|
||||
- Outbound text is chunked to `channels.slack.textChunkLimit` (default 4000).
|
||||
- Optional newline chunking: set `channels.slack.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- Media uploads are capped by `channels.slack.mediaMaxMb` (default 20).
|
||||
|
||||
## Reply threading
|
||||
By default, Bot replies in the main channel. Use `channels.slack.replyToMode` to control automatic threading:
|
||||
|
||||
| Mode | Behavior |
|
||||
| --- | --- |
|
||||
| `off` | **Default.** Reply in main channel. Only thread if the triggering message was already in a thread. |
|
||||
| `first` | First reply goes to thread (under the triggering message), subsequent replies go to main channel. Useful for keeping context visible while avoiding thread clutter. |
|
||||
| `all` | All replies go to thread. Keeps conversations contained but may reduce visibility. |
|
||||
|
||||
The mode applies to both auto-replies and agent tool calls (`slack sendMessage`).
|
||||
|
||||
### Per-chat-type threading
|
||||
You can configure different threading behavior per chat type by setting `channels.slack.replyToModeByChatType`:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
replyToMode: "off", // default for channels
|
||||
replyToModeByChatType: {
|
||||
direct: "all", // DMs always thread
|
||||
group: "first" // group DMs/MPIM thread first reply
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Supported chat types:
|
||||
- `direct`: 1:1 DMs (Slack `im`)
|
||||
- `group`: group DMs / MPIMs (Slack `mpim`)
|
||||
- `channel`: standard channels (public/private)
|
||||
|
||||
Precedence:
|
||||
1) `replyToModeByChatType.<chatType>`
|
||||
2) `replyToMode`
|
||||
3) Provider default (`off`)
|
||||
|
||||
Legacy `channels.slack.dm.replyToMode` is still accepted as a fallback for `direct` when no chat-type override is set.
|
||||
|
||||
Examples:
|
||||
|
||||
Thread DMs only:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
replyToMode: "off",
|
||||
replyToModeByChatType: { direct: "all" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Thread group DMs but keep channels in the root:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
replyToMode: "off",
|
||||
replyToModeByChatType: { group: "first" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Make channels thread, keep DMs in the root:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
replyToMode: "first",
|
||||
replyToModeByChatType: { direct: "off", group: "off" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Manual threading tags
|
||||
For fine-grained control, use these tags in agent responses:
|
||||
- `[[reply_to_current]]` — reply to the triggering message (start/continue thread).
|
||||
- `[[reply_to:<id>]]` — reply to a specific message id.
|
||||
|
||||
## Sessions + routing
|
||||
- DMs share the `main` session (like WhatsApp/Telegram).
|
||||
- Channels map to `agent:<agentId>:slack:channel:<channelId>` sessions.
|
||||
- Slash commands use `agent:<agentId>:slack:slash:<userId>` sessions (prefix configurable via `channels.slack.slashCommand.sessionPrefix`).
|
||||
- If Slack doesn’t provide `channel_type`, Bot infers it from the channel ID prefix (`D`, `C`, `G`) and defaults to `channel` to keep session keys stable.
|
||||
- Native command registration uses `commands.native` (global default `"auto"` → Slack off) and can be overridden per-workspace with `channels.slack.commands.native`. Text commands require standalone `/...` messages and can be disabled with `commands.text: false`. Slack slash commands are managed in the Slack app and are not removed automatically. Use `commands.useAccessGroups: false` to bypass access-group checks for commands.
|
||||
- Full command list + config: [Slash commands](/tools/slash-commands)
|
||||
|
||||
## DM security (pairing)
|
||||
- Default: `channels.slack.dm.policy="pairing"` — unknown DM senders get a pairing code (expires after 1 hour).
|
||||
- Approve via: `bot pairing approve slack <code>`.
|
||||
- To allow anyone: set `channels.slack.dm.policy="open"` and `channels.slack.dm.allowFrom=["*"]`.
|
||||
- `channels.slack.dm.allowFrom` accepts user IDs, @handles, or emails (resolved at startup when tokens allow). The wizard accepts usernames and resolves them to ids during setup when tokens allow.
|
||||
|
||||
## Group policy
|
||||
- `channels.slack.groupPolicy` controls channel handling (`open|disabled|allowlist`).
|
||||
- `allowlist` requires channels to be listed in `channels.slack.channels`.
|
||||
- If you only set `SLACK_BOT_TOKEN`/`SLACK_APP_TOKEN` and never create a `channels.slack` section,
|
||||
the runtime defaults `groupPolicy` to `open`. Add `channels.slack.groupPolicy`,
|
||||
`channels.defaults.groupPolicy`, or a channel allowlist to lock it down.
|
||||
- The configure wizard accepts `#channel` names and resolves them to IDs when possible
|
||||
(public + private); if multiple matches exist, it prefers the active channel.
|
||||
- On startup, Bot resolves channel/user names in allowlists to IDs (when tokens allow)
|
||||
and logs the mapping; unresolved entries are kept as typed.
|
||||
- To allow **no channels**, set `channels.slack.groupPolicy: "disabled"` (or keep an empty allowlist).
|
||||
|
||||
Channel options (`channels.slack.channels.<id>` or `channels.slack.channels.<name>`):
|
||||
- `allow`: allow/deny the channel when `groupPolicy="allowlist"`.
|
||||
- `requireMention`: mention gating for the channel.
|
||||
- `allowBots`: allow bot-authored messages in this channel (default: false).
|
||||
- `users`: optional per-channel user allowlist.
|
||||
- `skills`: skill filter (omit = all skills, empty = none).
|
||||
- `systemPrompt`: extra system prompt for the channel (combined with topic/purpose).
|
||||
- `enabled`: set `false` to disable the channel.
|
||||
|
||||
## Delivery targets
|
||||
Use these with cron/CLI sends:
|
||||
- `user:<id>` for DMs
|
||||
- `channel:<id>` for channels
|
||||
|
||||
## Tool actions
|
||||
Slack tool actions can be gated with `channels.slack.actions.*`:
|
||||
|
||||
| Action group | Default | Notes |
|
||||
| --- | --- | --- |
|
||||
| reactions | enabled | React + list reactions |
|
||||
| messages | enabled | Read/send/edit/delete |
|
||||
| pins | enabled | Pin/unpin/list |
|
||||
| memberInfo | enabled | Member info |
|
||||
| emojiList | enabled | Custom emoji list |
|
||||
|
||||
## Security notes
|
||||
- Writes default to the bot token so state-changing actions stay scoped to the
|
||||
app's bot permissions and identity.
|
||||
- Setting `userTokenReadOnly: false` allows the user token to be used for write
|
||||
operations when a bot token is unavailable, which means actions run with the
|
||||
installing user's access. Treat the user token as highly privileged and keep
|
||||
action gates and allowlists tight.
|
||||
- If you enable user-token writes, make sure the user token includes the write
|
||||
scopes you expect (`chat:write`, `reactions:write`, `pins:write`,
|
||||
`files:write`) or those operations will fail.
|
||||
|
||||
## Notes
|
||||
- Mention gating is controlled via `channels.slack.channels` (set `requireMention` to `true`); `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions.
|
||||
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
||||
- Reaction notifications follow `channels.slack.reactionNotifications` (use `reactionAllowlist` with mode `allowlist`).
|
||||
- Bot-authored messages are ignored by default; enable via `channels.slack.allowBots` or `channels.slack.channels.<id>.allowBots`.
|
||||
- Warning: If you allow replies to other bots (`channels.slack.allowBots=true` or `channels.slack.channels.<id>.allowBots=true`), prevent bot-to-bot reply loops with `requireMention`, `channels.slack.channels.<id>.users` allowlists, and/or clear guardrails in `AGENTS.md` and `SOUL.md`.
|
||||
- For the Slack tool, reaction removal semantics are in [/tools/reactions](/tools/reactions).
|
||||
- Attachments are downloaded to the media store when permitted and under the size limit.
|
||||
@@ -0,0 +1,543 @@
|
||||
---
|
||||
title: "Telegram (Bot API)"
|
||||
description: "Telegram bot support status, capabilities, and configuration"
|
||||
---
|
||||
|
||||
Status: production-ready for bot DMs + groups via grammY. Long-polling by default; webhook optional.
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Create a bot with **@BotFather** and copy the token.
|
||||
2) Set the token:
|
||||
- Env: `TELEGRAM_BOT_TOKEN=...`
|
||||
- Or config: `channels.telegram.botToken: "..."`.
|
||||
- If both are set, config takes precedence (env fallback is default-account only).
|
||||
3) Start the gateway.
|
||||
4) DM access is pairing by default; approve the pairing code on first contact.
|
||||
|
||||
Minimal config:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
botToken: "123:abc",
|
||||
dmPolicy: "pairing"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## What it is
|
||||
- A Telegram Bot API channel owned by the Gateway.
|
||||
- Deterministic routing: replies go back to Telegram; the model never chooses channels.
|
||||
- DMs share the agent's main session; groups stay isolated (`agent:<agentId>:telegram:group:<chatId>`).
|
||||
|
||||
## Setup (fast path)
|
||||
### 1) Create a bot token (BotFather)
|
||||
1) Open Telegram and chat with **@BotFather**.
|
||||
2) Run `/newbot`, then follow the prompts (name + username ending in `bot`).
|
||||
3) Copy the token and store it safely.
|
||||
|
||||
Optional BotFather settings:
|
||||
- `/setjoingroups` — allow/deny adding the bot to groups.
|
||||
- `/setprivacy` — control whether the bot sees all group messages.
|
||||
|
||||
### 2) Configure the token (env or config)
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
botToken: "123:abc",
|
||||
dmPolicy: "pairing",
|
||||
groups: { "*": { requireMention: true } }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Env option: `TELEGRAM_BOT_TOKEN=...` (works for the default account).
|
||||
If both env and config are set, config takes precedence.
|
||||
|
||||
Multi-account support: use `channels.telegram.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
||||
|
||||
3) Start the gateway. Telegram starts when a token is resolved (config first, env fallback).
|
||||
4) DM access defaults to pairing. Approve the code when the bot is first contacted.
|
||||
5) For groups: add the bot, decide privacy/admin behavior (below), then set `channels.telegram.groups` to control mention gating + allowlists.
|
||||
|
||||
## Token + privacy + permissions (Telegram side)
|
||||
|
||||
### Token creation (BotFather)
|
||||
- `/newbot` creates the bot and returns the token (keep it secret).
|
||||
- If a token leaks, revoke/regenerate it via @BotFather and update your config.
|
||||
|
||||
### Group message visibility (Privacy Mode)
|
||||
Telegram bots default to **Privacy Mode**, which limits which group messages they receive.
|
||||
If your bot must see *all* group messages, you have two options:
|
||||
- Disable privacy mode with `/setprivacy` **or**
|
||||
- Add the bot as a group **admin** (admin bots receive all messages).
|
||||
|
||||
**Note:** When you toggle privacy mode, Telegram requires removing + re‑adding the bot
|
||||
to each group for the change to take effect.
|
||||
|
||||
### Group permissions (admin rights)
|
||||
Admin status is set inside the group (Telegram UI). Admin bots always receive all
|
||||
group messages, so use admin if you need full visibility.
|
||||
|
||||
## How it works (behavior)
|
||||
- Inbound messages are normalized into the shared channel envelope with reply context and media placeholders.
|
||||
- Group replies require a mention by default (native @mention or `agents.list[].groupChat.mentionPatterns` / `messages.groupChat.mentionPatterns`).
|
||||
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
||||
- Replies always route back to the same Telegram chat.
|
||||
- Long-polling uses grammY runner with per-chat sequencing; overall concurrency is capped by `agents.defaults.maxConcurrent`.
|
||||
- Telegram Bot API does not support read receipts; there is no `sendReadReceipts` option.
|
||||
|
||||
## Formatting (Telegram HTML)
|
||||
- Outbound Telegram text uses `parse_mode: "HTML"` (Telegram’s supported tag subset).
|
||||
- Markdown-ish input is rendered into **Telegram-safe HTML** (bold/italic/strike/code/links); block elements are flattened to text with newlines/bullets.
|
||||
- Raw HTML from models is escaped to avoid Telegram parse errors.
|
||||
- If Telegram rejects the HTML payload, Bot retries the same message as plain text.
|
||||
|
||||
## Commands (native + custom)
|
||||
Bot registers native commands (like `/status`, `/reset`, `/model`) with Telegram’s bot menu on startup.
|
||||
You can add custom commands to the menu via config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
customCommands: [
|
||||
{ command: "backup", description: "Git backup" },
|
||||
{ command: "generate", description: "Create an image" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- `setMyCommands failed` in logs usually means outbound HTTPS/DNS is blocked to `api.telegram.org`.
|
||||
- If you see `sendMessage` or `sendChatAction` failures, check IPv6 routing and DNS.
|
||||
|
||||
More help: [Channel troubleshooting](/channels/troubleshooting).
|
||||
|
||||
Notes:
|
||||
- Custom commands are **menu entries only**; Bot does not implement them unless you handle them elsewhere.
|
||||
- Command names are normalized (leading `/` stripped, lowercased) and must match `a-z`, `0-9`, `_` (1–32 chars).
|
||||
- Custom commands **cannot override native commands**. Conflicts are ignored and logged.
|
||||
- If `commands.native` is disabled, only custom commands are registered (or cleared if none).
|
||||
|
||||
## Limits
|
||||
- Outbound text is chunked to `channels.telegram.textChunkLimit` (default 4000).
|
||||
- Optional newline chunking: set `channels.telegram.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- Media downloads/uploads are capped by `channels.telegram.mediaMaxMb` (default 5).
|
||||
- Telegram Bot API requests time out after `channels.telegram.timeoutSeconds` (default 500 via grammY). Set lower to avoid long hangs.
|
||||
- Group history context uses `channels.telegram.historyLimit` (or `channels.telegram.accounts.*.historyLimit`), falling back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||
- DM history can be limited with `channels.telegram.dmHistoryLimit` (user turns). Per-user overrides: `channels.telegram.dms["<user_id>"].historyLimit`.
|
||||
|
||||
## Group activation modes
|
||||
|
||||
By default, the bot only responds to mentions in groups (`@botname` or patterns in `agents.list[].groupChat.mentionPatterns`). To change this behavior:
|
||||
|
||||
### Via config (recommended)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
groups: {
|
||||
"-1001234567890": { requireMention: false } // always respond in this group
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Important:** Setting `channels.telegram.groups` creates an **allowlist** - only listed groups (or `"*"`) will be accepted.
|
||||
Forum topics inherit their parent group config (allowFrom, requireMention, skills, prompts) unless you add per-topic overrides under `channels.telegram.groups.<groupId>.topics.<topicId>`.
|
||||
|
||||
To allow all groups with always-respond:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
groups: {
|
||||
"*": { requireMention: false } // all groups, always respond
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To keep mention-only for all groups (default behavior):
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
groups: {
|
||||
"*": { requireMention: true } // or omit groups entirely
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Via command (session-level)
|
||||
|
||||
Send in the group:
|
||||
- `/activation always` - respond to all messages
|
||||
- `/activation mention` - require mentions (default)
|
||||
|
||||
**Note:** Commands update session state only. For persistent behavior across restarts, use config.
|
||||
|
||||
### Getting the group chat ID
|
||||
|
||||
Forward any message from the group to `@userinfobot` or `@getidsbot` on Telegram to see the chat ID (negative number like `-1001234567890`).
|
||||
|
||||
**Tip:** For your own user ID, DM the bot and it will reply with your user ID (pairing message), or use `/whoami` once commands are enabled.
|
||||
|
||||
**Privacy note:** `@userinfobot` is a third-party bot. If you prefer, add the bot to the group, send a message, and use `bot logs --follow` to read `chat.id`, or use the Bot API `getUpdates`.
|
||||
|
||||
## Config writes
|
||||
By default, Telegram is allowed to write config updates triggered by channel events or `/config set|unset`.
|
||||
|
||||
This happens when:
|
||||
- A group is upgraded to a supergroup and Telegram emits `migrate_to_chat_id` (chat ID changes). Bot can migrate `channels.telegram.groups` automatically.
|
||||
- You run `/config set` or `/config unset` in a Telegram chat (requires `commands.config: true`).
|
||||
|
||||
Disable with:
|
||||
```json5
|
||||
{
|
||||
channels: { telegram: { configWrites: false } }
|
||||
}
|
||||
```
|
||||
|
||||
## Topics (forum supergroups)
|
||||
Telegram forum topics include a `message_thread_id` per message. Bot:
|
||||
- Appends `:topic:<threadId>` to the Telegram group session key so each topic is isolated.
|
||||
- Sends typing indicators and replies with `message_thread_id` so responses stay in the topic.
|
||||
- General topic (thread id `1`) is special: message sends omit `message_thread_id` (Telegram rejects it), but typing indicators still include it.
|
||||
- Exposes `MessageThreadId` + `IsForum` in template context for routing/templating.
|
||||
- Topic-specific configuration is available under `channels.telegram.groups.<chatId>.topics.<threadId>` (skills, allowlists, auto-reply, system prompts, disable).
|
||||
- Topic configs inherit group settings (requireMention, allowlists, skills, prompts, enabled) unless overridden per topic.
|
||||
|
||||
Private chats can include `message_thread_id` in some edge cases. Bot keeps the DM session key unchanged, but still uses the thread id for replies/draft streaming when it is present.
|
||||
|
||||
## Inline Buttons
|
||||
|
||||
Telegram supports inline keyboards with callback buttons.
|
||||
|
||||
```json5
|
||||
{
|
||||
"channels": {
|
||||
"telegram": {
|
||||
"capabilities": {
|
||||
"inlineButtons": "allowlist"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For per-account configuration:
|
||||
```json5
|
||||
{
|
||||
"channels": {
|
||||
"telegram": {
|
||||
"accounts": {
|
||||
"main": {
|
||||
"capabilities": {
|
||||
"inlineButtons": "allowlist"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Scopes:
|
||||
- `off` — inline buttons disabled
|
||||
- `dm` — only DMs (group targets blocked)
|
||||
- `group` — only groups (DM targets blocked)
|
||||
- `all` — DMs + groups
|
||||
- `allowlist` — DMs + groups, but only senders allowed by `allowFrom`/`groupAllowFrom` (same rules as control commands)
|
||||
|
||||
Default: `allowlist`.
|
||||
Legacy: `capabilities: ["inlineButtons"]` = `inlineButtons: "all"`.
|
||||
|
||||
### Sending buttons
|
||||
|
||||
Use the message tool with the `buttons` parameter:
|
||||
|
||||
```json5
|
||||
{
|
||||
"action": "send",
|
||||
"channel": "telegram",
|
||||
"to": "123456789",
|
||||
"message": "Choose an option:",
|
||||
"buttons": [
|
||||
[
|
||||
{"text": "Yes", "callback_data": "yes"},
|
||||
{"text": "No", "callback_data": "no"}
|
||||
],
|
||||
[
|
||||
{"text": "Cancel", "callback_data": "cancel"}
|
||||
]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
When a user clicks a button, the callback data is sent back to the agent as a message with the format:
|
||||
`callback_data: value`
|
||||
|
||||
### Configuration options
|
||||
|
||||
Telegram capabilities can be configured at two levels (object form shown above; legacy string arrays still supported):
|
||||
|
||||
- `channels.telegram.capabilities`: Global default capability config applied to all Telegram accounts unless overridden.
|
||||
- `channels.telegram.accounts.<account>.capabilities`: Per-account capabilities that override the global defaults for that specific account.
|
||||
|
||||
Use the global setting when all Telegram bots/accounts should behave the same. Use per-account configuration when different bots need different behaviors (for example, one account only handles DMs while another is allowed in groups).
|
||||
## Access control (DMs + groups)
|
||||
|
||||
### DM access
|
||||
- Default: `channels.telegram.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
- `bot pairing list telegram`
|
||||
- `bot pairing approve telegram <CODE>`
|
||||
- Pairing is the default token exchange used for Telegram DMs. Details: [Pairing](/start/pairing)
|
||||
- `channels.telegram.allowFrom` accepts numeric user IDs (recommended) or `@username` entries. It is **not** the bot username; use the human sender’s ID. The wizard accepts `@username` and resolves it to the numeric ID when possible.
|
||||
|
||||
#### Finding your Telegram user ID
|
||||
Safer (no third-party bot):
|
||||
1) Start the gateway and DM your bot.
|
||||
2) Run `bot logs --follow` and look for `from.id`.
|
||||
|
||||
Alternate (official Bot API):
|
||||
1) DM your bot.
|
||||
2) Fetch updates with your bot token and read `message.from.id`:
|
||||
```bash
|
||||
curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
```
|
||||
|
||||
Third-party (less private):
|
||||
- DM `@userinfobot` or `@getidsbot` and use the returned user id.
|
||||
|
||||
### Group access
|
||||
|
||||
Two independent controls:
|
||||
|
||||
**1. Which groups are allowed** (group allowlist via `channels.telegram.groups`):
|
||||
- No `groups` config = all groups allowed
|
||||
- With `groups` config = only listed groups or `"*"` are allowed
|
||||
- Example: `"groups": { "-1001234567890": {}, "*": {} }` allows all groups
|
||||
|
||||
**2. Which senders are allowed** (sender filtering via `channels.telegram.groupPolicy`):
|
||||
- `"open"` = all senders in allowed groups can message
|
||||
- `"allowlist"` = only senders in `channels.telegram.groupAllowFrom` can message
|
||||
- `"disabled"` = no group messages accepted at all
|
||||
Default is `groupPolicy: "allowlist"` (blocked unless you add `groupAllowFrom`).
|
||||
|
||||
Most users want: `groupPolicy: "allowlist"` + `groupAllowFrom` + specific groups listed in `channels.telegram.groups`
|
||||
|
||||
## Long-polling vs webhook
|
||||
- Default: long-polling (no public URL required).
|
||||
- Webhook mode: set `channels.telegram.webhookUrl` (optionally `channels.telegram.webhookSecret` + `channels.telegram.webhookPath`).
|
||||
- The local listener binds to `0.0.0.0:8787` and serves `POST /telegram-webhook` by default.
|
||||
- If your public URL is different, use a reverse proxy and point `channels.telegram.webhookUrl` at the public endpoint.
|
||||
|
||||
## Reply threading
|
||||
Telegram supports optional threaded replies via tags:
|
||||
- `[[reply_to_current]]` -- reply to the triggering message.
|
||||
- `[[reply_to:<id>]]` -- reply to a specific message id.
|
||||
|
||||
Controlled by `channels.telegram.replyToMode`:
|
||||
- `first` (default), `all`, `off`.
|
||||
|
||||
## Audio messages (voice vs file)
|
||||
Telegram distinguishes **voice notes** (round bubble) from **audio files** (metadata card).
|
||||
Bot defaults to audio files for backward compatibility.
|
||||
|
||||
To force a voice note bubble in agent replies, include this tag anywhere in the reply:
|
||||
- `[[audio_as_voice]]` — send audio as a voice note instead of a file.
|
||||
|
||||
The tag is stripped from the delivered text. Other channels ignore this tag.
|
||||
|
||||
For message tool sends, set `asVoice: true` with a voice-compatible audio `media` URL
|
||||
(`message` is optional when media is present):
|
||||
|
||||
```json5
|
||||
{
|
||||
"action": "send",
|
||||
"channel": "telegram",
|
||||
"to": "123456789",
|
||||
"media": "https://example.com/voice.ogg",
|
||||
"asVoice": true
|
||||
}
|
||||
```
|
||||
|
||||
## Streaming (drafts)
|
||||
Telegram can stream **draft bubbles** while the agent is generating a response.
|
||||
Bot uses Bot API `sendMessageDraft` (not real messages) and then sends the
|
||||
final reply as a normal message.
|
||||
|
||||
Requirements (Telegram Bot API 9.3+):
|
||||
- **Private chats with topics enabled** (forum topic mode for the bot).
|
||||
- Incoming messages must include `message_thread_id` (private topic thread).
|
||||
- Streaming is ignored for groups/supergroups/channels.
|
||||
|
||||
Config:
|
||||
- `channels.telegram.streamMode: "off" | "partial" | "block"` (default: `partial`)
|
||||
- `partial`: update the draft bubble with the latest streaming text.
|
||||
- `block`: update the draft bubble in larger blocks (chunked).
|
||||
- `off`: disable draft streaming.
|
||||
- Optional (only for `streamMode: "block"`):
|
||||
- `channels.telegram.draftChunk: { minChars?, maxChars?, breakPreference? }`
|
||||
- defaults: `minChars: 200`, `maxChars: 800`, `breakPreference: "paragraph"` (clamped to `channels.telegram.textChunkLimit`).
|
||||
|
||||
Note: draft streaming is separate from **block streaming** (channel messages).
|
||||
Block streaming is off by default and requires `channels.telegram.blockStreaming: true`
|
||||
if you want early Telegram messages instead of draft updates.
|
||||
|
||||
Reasoning stream (Telegram only):
|
||||
- `/reasoning stream` streams reasoning into the draft bubble while the reply is
|
||||
generating, then sends the final answer without reasoning.
|
||||
- If `channels.telegram.streamMode` is `off`, reasoning stream is disabled.
|
||||
More context: [Streaming + chunking](/concepts/streaming).
|
||||
|
||||
## Retry policy
|
||||
Outbound Telegram API calls retry on transient network/429 errors with exponential backoff and jitter. Configure via `channels.telegram.retry`. See [Retry policy](/concepts/retry).
|
||||
|
||||
## Agent tool (messages + reactions)
|
||||
- Tool: `telegram` with `sendMessage` action (`to`, `content`, optional `mediaUrl`, `replyToMessageId`, `messageThreadId`).
|
||||
- Tool: `telegram` with `react` action (`chatId`, `messageId`, `emoji`).
|
||||
- Tool: `telegram` with `deleteMessage` action (`chatId`, `messageId`).
|
||||
- Reaction removal semantics: see [/tools/reactions](/tools/reactions).
|
||||
- Tool gating: `channels.telegram.actions.reactions`, `channels.telegram.actions.sendMessage`, `channels.telegram.actions.deleteMessage` (default: enabled).
|
||||
|
||||
## Reaction notifications
|
||||
|
||||
**How reactions work:**
|
||||
Telegram reactions arrive as **separate `message_reaction` events**, not as properties in message payloads. When a user adds a reaction, Bot:
|
||||
|
||||
1. Receives the `message_reaction` update from Telegram API
|
||||
2. Converts it to a **system event** with format: `"Telegram reaction added: {emoji} by {user} on msg {id}"`
|
||||
3. Enqueues the system event using the **same session key** as regular messages
|
||||
4. When the next message arrives in that conversation, system events are drained and prepended to the agent's context
|
||||
|
||||
The agent sees reactions as **system notifications** in the conversation history, not as message metadata.
|
||||
|
||||
**Configuration:**
|
||||
- `channels.telegram.reactionNotifications`: Controls which reactions trigger notifications
|
||||
- `"off"` — ignore all reactions
|
||||
- `"own"` — notify when users react to bot messages (best-effort; in-memory) (default)
|
||||
- `"all"` — notify for all reactions
|
||||
|
||||
- `channels.telegram.reactionLevel`: Controls agent's reaction capability
|
||||
- `"off"` — agent cannot react to messages
|
||||
- `"ack"` — bot sends acknowledgment reactions (👀 while processing) (default)
|
||||
- `"minimal"` — agent can react sparingly (guideline: 1 per 5-10 exchanges)
|
||||
- `"extensive"` — agent can react liberally when appropriate
|
||||
|
||||
**Forum groups:** Reactions in forum groups include `message_thread_id` and use session keys like `agent:main:telegram:group:{chatId}:topic:{threadId}`. This ensures reactions and messages in the same topic stay together.
|
||||
|
||||
**Example config:**
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
reactionNotifications: "all", // See all reactions
|
||||
reactionLevel: "minimal" // Agent can react sparingly
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Requirements:**
|
||||
- Telegram bots must explicitly request `message_reaction` in `allowed_updates` (configured automatically by Bot)
|
||||
- For webhook mode, reactions are included in the webhook `allowed_updates`
|
||||
- For polling mode, reactions are included in the `getUpdates` `allowed_updates`
|
||||
|
||||
## Delivery targets (CLI/cron)
|
||||
- Use a chat id (`123456789`) or a username (`@name`) as the target.
|
||||
- Example: `bot message send --channel telegram --target 123456789 --message "hi"`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Bot doesn’t respond to non-mention messages in a group:**
|
||||
- If you set `channels.telegram.groups.*.requireMention=false`, Telegram’s Bot API **privacy mode** must be disabled.
|
||||
- BotFather: `/setprivacy` → **Disable** (then remove + re-add the bot to the group)
|
||||
- `bot channels status` shows a warning when config expects unmentioned group messages.
|
||||
- `bot channels status --probe` can additionally check membership for explicit numeric group IDs (it can’t audit wildcard `"*"` rules).
|
||||
- Quick test: `/activation always` (session-only; use config for persistence)
|
||||
|
||||
**Bot not seeing group messages at all:**
|
||||
- If `channels.telegram.groups` is set, the group must be listed or use `"*"`
|
||||
- Check Privacy Settings in @BotFather → "Group Privacy" should be **OFF**
|
||||
- Verify bot is actually a member (not just an admin with no read access)
|
||||
- Check gateway logs: `bot logs --follow` (look for "skipping group message")
|
||||
|
||||
**Bot responds to mentions but not `/activation always`:**
|
||||
- The `/activation` command updates session state but doesn't persist to config
|
||||
- For persistent behavior, add group to `channels.telegram.groups` with `requireMention: false`
|
||||
|
||||
**Commands like `/status` don't work:**
|
||||
- Make sure your Telegram user ID is authorized (via pairing or `channels.telegram.allowFrom`)
|
||||
- Commands require authorization even in groups with `groupPolicy: "open"`
|
||||
|
||||
**Long-polling aborts immediately on Node 22+ (often with proxies/custom fetch):**
|
||||
- Node 22+ is stricter about `AbortSignal` instances; foreign signals can abort `fetch` calls right away.
|
||||
- Upgrade to a Bot build that normalizes abort signals, or run the gateway on Node 20 until you can upgrade.
|
||||
|
||||
**Bot starts, then silently stops responding (or logs `HttpError: Network request ... failed`):**
|
||||
- Some hosts resolve `api.telegram.org` to IPv6 first. If your server does not have working IPv6 egress, grammY can get stuck on IPv6-only requests.
|
||||
- Fix by enabling IPv6 egress **or** forcing IPv4 resolution for `api.telegram.org` (for example, add an `/etc/hosts` entry using the IPv4 A record, or prefer IPv4 in your OS DNS stack), then restart the gateway.
|
||||
- Quick check: `dig +short api.telegram.org A` and `dig +short api.telegram.org AAAA` to confirm what DNS returns.
|
||||
|
||||
## Configuration reference (Telegram)
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
- `channels.telegram.enabled`: enable/disable channel startup.
|
||||
- `channels.telegram.botToken`: bot token (BotFather).
|
||||
- `channels.telegram.tokenFile`: read token from file path.
|
||||
- `channels.telegram.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing).
|
||||
- `channels.telegram.allowFrom`: DM allowlist (ids/usernames). `open` requires `"*"`.
|
||||
- `channels.telegram.groupPolicy`: `open | allowlist | disabled` (default: allowlist).
|
||||
- `channels.telegram.groupAllowFrom`: group sender allowlist (ids/usernames).
|
||||
- `channels.telegram.groups`: per-group defaults + allowlist (use `"*"` for global defaults).
|
||||
- `channels.telegram.groups.<id>.requireMention`: mention gating default.
|
||||
- `channels.telegram.groups.<id>.skills`: skill filter (omit = all skills, empty = none).
|
||||
- `channels.telegram.groups.<id>.allowFrom`: per-group sender allowlist override.
|
||||
- `channels.telegram.groups.<id>.systemPrompt`: extra system prompt for the group.
|
||||
- `channels.telegram.groups.<id>.enabled`: disable the group when `false`.
|
||||
- `channels.telegram.groups.<id>.topics.<threadId>.*`: per-topic overrides (same fields as group).
|
||||
- `channels.telegram.groups.<id>.topics.<threadId>.requireMention`: per-topic mention gating override.
|
||||
- `channels.telegram.capabilities.inlineButtons`: `off | dm | group | all | allowlist` (default: allowlist).
|
||||
- `channels.telegram.accounts.<account>.capabilities.inlineButtons`: per-account override.
|
||||
- `channels.telegram.replyToMode`: `off | first | all` (default: `first`).
|
||||
- `channels.telegram.textChunkLimit`: outbound chunk size (chars).
|
||||
- `channels.telegram.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.telegram.linkPreview`: toggle link previews for outbound messages (default: true).
|
||||
- `channels.telegram.streamMode`: `off | partial | block` (draft streaming).
|
||||
- `channels.telegram.mediaMaxMb`: inbound/outbound media cap (MB).
|
||||
- `channels.telegram.retry`: retry policy for outbound Telegram API calls (attempts, minDelayMs, maxDelayMs, jitter).
|
||||
- `channels.telegram.proxy`: proxy URL for Bot API calls (SOCKS/HTTP).
|
||||
- `channels.telegram.webhookUrl`: enable webhook mode.
|
||||
- `channels.telegram.webhookSecret`: webhook secret (optional).
|
||||
- `channels.telegram.webhookPath`: local webhook path (default `/telegram-webhook`).
|
||||
- `channels.telegram.actions.reactions`: gate Telegram tool reactions.
|
||||
- `channels.telegram.actions.sendMessage`: gate Telegram tool message sends.
|
||||
- `channels.telegram.actions.deleteMessage`: gate Telegram tool message deletes.
|
||||
- `channels.telegram.reactionNotifications`: `off | own | all` — control which reactions trigger system events (default: `own` when not set).
|
||||
- `channels.telegram.reactionLevel`: `off | ack | minimal | extensive` — control agent's reaction capability (default: `minimal` when not set).
|
||||
|
||||
Related global options:
|
||||
- `agents.list[].groupChat.mentionPatterns` (mention gating patterns).
|
||||
- `messages.groupChat.mentionPatterns` (global fallback).
|
||||
- `commands.native` (defaults to `"auto"` → on for Telegram/Discord, off for Slack), `commands.text`, `commands.useAccessGroups` (command behavior). Override with `channels.telegram.commands.native`.
|
||||
- `messages.responsePrefix`, `messages.ackReaction`, `messages.ackReactionScope`, `messages.removeAckAfterReply`.
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
title: "Tlon (plugin)"
|
||||
description: "Tlon/Urbit support status, capabilities, and configuration"
|
||||
---
|
||||
|
||||
Tlon is a decentralized messenger built on Urbit. Bot connects to your Urbit ship and can
|
||||
respond to DMs and group chat messages. Group replies require an @ mention by default and can
|
||||
be further restricted via allowlists.
|
||||
|
||||
Status: supported via plugin. DMs, group mentions, thread replies, and text-only media fallback
|
||||
(URL appended to caption). Reactions, polls, and native media uploads are not supported.
|
||||
|
||||
## Plugin required
|
||||
|
||||
Tlon ships as a plugin and is not bundled with the core install.
|
||||
|
||||
Install via CLI (npm registry):
|
||||
|
||||
```bash
|
||||
bot plugins install @bot/tlon
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
bot plugins install ./extensions/tlon
|
||||
```
|
||||
|
||||
Details: [Plugins](/plugin)
|
||||
|
||||
## Setup
|
||||
|
||||
1) Install the Tlon plugin.
|
||||
2) Gather your ship URL and login code.
|
||||
3) Configure `channels.tlon`.
|
||||
4) Restart the gateway.
|
||||
5) DM the bot or mention it in a group channel.
|
||||
|
||||
Minimal config (single account):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
enabled: true,
|
||||
ship: "~sampel-palnet",
|
||||
url: "https://your-ship-host",
|
||||
code: "lidlut-tabwed-pillex-ridrup"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Group channels
|
||||
|
||||
Auto-discovery is enabled by default. You can also pin channels manually:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
groupChannels: [
|
||||
"chat/~host-ship/general",
|
||||
"chat/~host-ship/support"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Disable auto-discovery:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
autoDiscoverChannels: false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Access control
|
||||
|
||||
DM allowlist (empty = allow all):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
dmAllowlist: ["~zod", "~nec"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Group authorization (restricted by default):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
defaultAuthorizedShips: ["~zod"],
|
||||
authorization: {
|
||||
channelRules: {
|
||||
"chat/~host-ship/general": {
|
||||
mode: "restricted",
|
||||
allowedShips: ["~zod", "~nec"]
|
||||
},
|
||||
"chat/~host-ship/announcements": {
|
||||
mode: "open"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Delivery targets (CLI/cron)
|
||||
|
||||
Use these with `bot message send` or cron delivery:
|
||||
|
||||
- DM: `~sampel-palnet` or `dm/~sampel-palnet`
|
||||
- Group: `chat/~host-ship/channel` or `group:~host-ship/channel`
|
||||
|
||||
## Notes
|
||||
|
||||
- Group replies require a mention (e.g. `~your-bot-ship`) to respond.
|
||||
- Thread replies: if the inbound message is in a thread, Bot replies in-thread.
|
||||
- Media: `sendMedia` falls back to text + URL (no native upload).
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: "Channel troubleshooting"
|
||||
description: "Channel-specific troubleshooting shortcuts (Discord/Telegram/WhatsApp)"
|
||||
---
|
||||
|
||||
Start with:
|
||||
|
||||
```bash
|
||||
bot doctor
|
||||
bot channels status --probe
|
||||
```
|
||||
|
||||
`channels status --probe` prints warnings when it can detect common channel misconfigurations, and includes small live checks (credentials, some permissions/membership).
|
||||
|
||||
## Channels
|
||||
- Discord: [/channels/discord#troubleshooting](/channels/discord#troubleshooting)
|
||||
- Telegram: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting)
|
||||
- WhatsApp: [/channels/whatsapp#troubleshooting-quick](/channels/whatsapp#troubleshooting-quick)
|
||||
|
||||
## Telegram quick fixes
|
||||
- Logs show `HttpError: Network request for 'sendMessage' failed` or `sendChatAction` → check IPv6 DNS. If `api.telegram.org` resolves to IPv6 first and the host lacks IPv6 egress, force IPv4 or enable IPv6. See [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting).
|
||||
- Logs show `setMyCommands failed` → check outbound HTTPS and DNS reachability to `api.telegram.org` (common on locked-down VPS or proxies).
|
||||
@@ -0,0 +1,359 @@
|
||||
---
|
||||
title: "WhatsApp (web channel)"
|
||||
description: "WhatsApp (web channel) integration: login, inbox, replies, media, and ops"
|
||||
---
|
||||
|
||||
Status: WhatsApp Web via Baileys only. Gateway owns the session(s).
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Use a **separate phone number** if possible (recommended).
|
||||
2) Configure WhatsApp in `~/.bot/bot.json`.
|
||||
3) Run `bot channels login` to scan the QR code (Linked Devices).
|
||||
4) Start the gateway.
|
||||
|
||||
Minimal config:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["+15551234567"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Goals
|
||||
- Multiple WhatsApp accounts (multi-account) in one Gateway process.
|
||||
- Deterministic routing: replies return to WhatsApp, no model routing.
|
||||
- Model sees enough context to understand quoted replies.
|
||||
|
||||
## Config writes
|
||||
By default, WhatsApp is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||
|
||||
Disable with:
|
||||
```json5
|
||||
{
|
||||
channels: { whatsapp: { configWrites: false } }
|
||||
}
|
||||
```
|
||||
|
||||
## Architecture (who owns what)
|
||||
- **Gateway** owns the Baileys socket and inbox loop.
|
||||
- **CLI / macOS app** talk to the gateway; no direct Baileys use.
|
||||
- **Active listener** is required for outbound sends; otherwise send fails fast.
|
||||
|
||||
## Getting a phone number (two modes)
|
||||
|
||||
WhatsApp requires a real mobile number for verification. VoIP and virtual numbers are usually blocked. There are two supported ways to run Bot on WhatsApp:
|
||||
|
||||
### Dedicated number (recommended)
|
||||
Use a **separate phone number** for Bot. Best UX, clean routing, no self-chat quirks. Ideal setup: **spare/old Android phone + eSIM**. Leave it on Wi‑Fi and power, and link it via QR.
|
||||
|
||||
**WhatsApp Business:** You can use WhatsApp Business on the same device with a different number. Great for keeping your personal WhatsApp separate — install WhatsApp Business and register the Bot number there.
|
||||
|
||||
**Sample config (dedicated number, single-user allowlist):**
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["+15551234567"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Pairing mode (optional):**
|
||||
If you want pairing instead of allowlist, set `channels.whatsapp.dmPolicy` to `pairing`. Unknown senders get a pairing code; approve with:
|
||||
`bot pairing approve whatsapp <code>`
|
||||
|
||||
### Personal number (fallback)
|
||||
Quick fallback: run Bot on **your own number**. Message yourself (WhatsApp “Message yourself”) for testing so you don’t spam contacts. Expect to read verification codes on your main phone during setup and experiments. **Must enable self-chat mode.**
|
||||
When the wizard asks for your personal WhatsApp number, enter the phone you will message from (the owner/sender), not the assistant number.
|
||||
|
||||
**Sample config (personal number, self-chat):**
|
||||
```json
|
||||
{
|
||||
"whatsapp": {
|
||||
"selfChatMode": true,
|
||||
"dmPolicy": "allowlist",
|
||||
"allowFrom": ["+15551234567"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Self-chat replies default to `[{identity.name}]` when set (otherwise `[bot]`)
|
||||
if `messages.responsePrefix` is unset. Set it explicitly to customize or disable
|
||||
the prefix (use `""` to remove it).
|
||||
|
||||
### Number sourcing tips
|
||||
- **Local eSIM** from your country's mobile carrier (most reliable)
|
||||
- Austria: [hot.at](https://www.hot.at)
|
||||
- UK: [giffgaff](https://www.giffgaff.com) — free SIM, no contract
|
||||
- **Prepaid SIM** — cheap, just needs to receive one SMS for verification
|
||||
|
||||
**Avoid:** TextNow, Google Voice, most "free SMS" services — WhatsApp blocks these aggressively.
|
||||
|
||||
**Tip:** The number only needs to receive one verification SMS. After that, WhatsApp Web sessions persist via `creds.json`.
|
||||
|
||||
## Why Not Twilio?
|
||||
- Early Bot builds supported Twilio’s WhatsApp Business integration.
|
||||
- WhatsApp Business numbers are a poor fit for a personal assistant.
|
||||
- Meta enforces a 24‑hour reply window; if you haven’t responded in the last 24 hours, the business number can’t initiate new messages.
|
||||
- High-volume or “chatty” usage triggers aggressive blocking, because business accounts aren’t meant to send dozens of personal assistant messages.
|
||||
- Result: unreliable delivery and frequent blocks, so support was removed.
|
||||
|
||||
## Login + credentials
|
||||
- Login command: `bot channels login` (QR via Linked Devices).
|
||||
- Multi-account login: `bot channels login --account <id>` (`<id>` = `accountId`).
|
||||
- Default account (when `--account` is omitted): `default` if present, otherwise the first configured account id (sorted).
|
||||
- Credentials stored in `~/.bot/credentials/whatsapp/<accountId>/creds.json`.
|
||||
- Backup copy at `creds.json.bak` (restored on corruption).
|
||||
- Legacy compatibility: older installs stored Baileys files directly in `~/.bot/credentials/`.
|
||||
- Logout: `bot channels logout` (or `--account <id>`) deletes WhatsApp auth state (but keeps shared `oauth.json`).
|
||||
- Logged-out socket => error instructs re-link.
|
||||
|
||||
## Inbound flow (DM + group)
|
||||
- WhatsApp events come from `messages.upsert` (Baileys).
|
||||
- Inbox listeners are detached on shutdown to avoid accumulating event handlers in tests/restarts.
|
||||
- Status/broadcast chats are ignored.
|
||||
- Direct chats use E.164; groups use group JID.
|
||||
- **DM policy**: `channels.whatsapp.dmPolicy` controls direct chat access (default: `pairing`).
|
||||
- Pairing: unknown senders get a pairing code (approve via `bot pairing approve whatsapp <code>`; codes expire after 1 hour).
|
||||
- Open: requires `channels.whatsapp.allowFrom` to include `"*"`.
|
||||
- Self messages are always allowed; “self-chat mode” still requires `channels.whatsapp.allowFrom` to include your own number.
|
||||
|
||||
### Personal-number mode (fallback)
|
||||
If you run Bot on your **personal WhatsApp number**, enable `channels.whatsapp.selfChatMode` (see sample above).
|
||||
|
||||
Behavior:
|
||||
- Outbound DMs never trigger pairing replies (prevents spamming contacts).
|
||||
- Inbound unknown senders still follow `channels.whatsapp.dmPolicy`.
|
||||
- Self-chat mode (allowFrom includes your number) avoids auto read receipts and ignores mention JIDs.
|
||||
- Read receipts sent for non-self-chat DMs.
|
||||
|
||||
## Read receipts
|
||||
By default, the gateway marks inbound WhatsApp messages as read (blue ticks) once they are accepted.
|
||||
|
||||
Disable globally:
|
||||
```json5
|
||||
{
|
||||
channels: { whatsapp: { sendReadReceipts: false } }
|
||||
}
|
||||
```
|
||||
|
||||
Disable per account:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
accounts: {
|
||||
personal: { sendReadReceipts: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Self-chat mode always skips read receipts.
|
||||
|
||||
## WhatsApp FAQ: sending messages + pairing
|
||||
|
||||
**Will Bot message random contacts when I link WhatsApp?**
|
||||
No. Default DM policy is **pairing**, so unknown senders only get a pairing code and their message is **not processed**. Bot only replies to chats it receives, or to sends you explicitly trigger (agent/CLI).
|
||||
|
||||
**How does pairing work on WhatsApp?**
|
||||
Pairing is a DM gate for unknown senders:
|
||||
- First DM from a new sender returns a short code (message is not processed).
|
||||
- Approve with: `bot pairing approve whatsapp <code>` (list with `bot pairing list whatsapp`).
|
||||
- Codes expire after 1 hour; pending requests are capped at 3 per channel.
|
||||
|
||||
**Can multiple people use different Bots on one WhatsApp number?**
|
||||
Yes, by routing each sender to a different agent via `bindings` (peer `kind: "dm"`, sender E.164 like `+15551234567`). Replies still come from the **same WhatsApp account**, and direct chats collapse to each agent’s main session, so use **one agent per person**. DM access control (`dmPolicy`/`allowFrom`) is global per WhatsApp account. See [Multi-Agent Routing](/concepts/multi-agent).
|
||||
|
||||
**Why do you ask for my phone number in the wizard?**
|
||||
The wizard uses it to set your **allowlist/owner** so your own DMs are permitted. It’s not used for auto-sending. If you run on your personal WhatsApp number, use that same number and enable `channels.whatsapp.selfChatMode`.
|
||||
|
||||
## Message normalization (what the model sees)
|
||||
- `Body` is the current message body with envelope.
|
||||
- Quoted reply context is **always appended**:
|
||||
```
|
||||
[Replying to +1555 id:ABC123]
|
||||
<quoted text or <media:...>>
|
||||
[/Replying]
|
||||
```
|
||||
- Reply metadata also set:
|
||||
- `ReplyToId` = stanzaId
|
||||
- `ReplyToBody` = quoted body or media placeholder
|
||||
- `ReplyToSender` = E.164 when known
|
||||
- Media-only inbound messages use placeholders:
|
||||
- `<media:image|video|audio|document|sticker>`
|
||||
|
||||
## Groups
|
||||
- Groups map to `agent:<agentId>:whatsapp:group:<jid>` sessions.
|
||||
- Group policy: `channels.whatsapp.groupPolicy = open|disabled|allowlist` (default `allowlist`).
|
||||
- Activation modes:
|
||||
- `mention` (default): requires @mention or regex match.
|
||||
- `always`: always triggers.
|
||||
- `/activation mention|always` is owner-only and must be sent as a standalone message.
|
||||
- Owner = `channels.whatsapp.allowFrom` (or self E.164 if unset).
|
||||
- **History injection** (pending-only):
|
||||
- Recent *unprocessed* messages (default 50) inserted under:
|
||||
`[Chat messages since your last reply - for context]` (messages already in the session are not re-injected)
|
||||
- Current message under:
|
||||
`[Current message - respond to this]`
|
||||
- Sender suffix appended: `[from: Name (+E164)]`
|
||||
- Group metadata cached 5 min (subject + participants).
|
||||
|
||||
## Reply delivery (threading)
|
||||
- WhatsApp Web sends standard messages (no quoted reply threading in the current gateway).
|
||||
- Reply tags are ignored on this channel.
|
||||
|
||||
## Acknowledgment reactions (auto-react on receipt)
|
||||
|
||||
WhatsApp can automatically send emoji reactions to incoming messages immediately upon receipt, before the bot generates a reply. This provides instant feedback to users that their message was received.
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
{
|
||||
"whatsapp": {
|
||||
"ackReaction": {
|
||||
"emoji": "👀",
|
||||
"direct": true,
|
||||
"group": "mentions"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `emoji` (string): Emoji to use for acknowledgment (e.g., "👀", "✅", "📨"). Empty or omitted = feature disabled.
|
||||
- `direct` (boolean, default: `true`): Send reactions in direct/DM chats.
|
||||
- `group` (string, default: `"mentions"`): Group chat behavior:
|
||||
- `"always"`: React to all group messages (even without @mention)
|
||||
- `"mentions"`: React only when bot is @mentioned
|
||||
- `"never"`: Never react in groups
|
||||
|
||||
**Per-account override:**
|
||||
```json
|
||||
{
|
||||
"whatsapp": {
|
||||
"accounts": {
|
||||
"work": {
|
||||
"ackReaction": {
|
||||
"emoji": "✅",
|
||||
"direct": false,
|
||||
"group": "always"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Behavior notes:**
|
||||
- Reactions are sent **immediately** upon message receipt, before typing indicators or bot replies.
|
||||
- In groups with `requireMention: false` (activation: always), `group: "mentions"` will react to all messages (not just @mentions).
|
||||
- Fire-and-forget: reaction failures are logged but don't prevent the bot from replying.
|
||||
- Participant JID is automatically included for group reactions.
|
||||
- WhatsApp ignores `messages.ackReaction`; use `channels.whatsapp.ackReaction` instead.
|
||||
|
||||
## Agent tool (reactions)
|
||||
- Tool: `whatsapp` with `react` action (`chatJid`, `messageId`, `emoji`, optional `remove`).
|
||||
- Optional: `participant` (group sender), `fromMe` (reacting to your own message), `accountId` (multi-account).
|
||||
- Reaction removal semantics: see [/tools/reactions](/tools/reactions).
|
||||
- Tool gating: `channels.whatsapp.actions.reactions` (default: enabled).
|
||||
|
||||
## Limits
|
||||
- Outbound text is chunked to `channels.whatsapp.textChunkLimit` (default 4000).
|
||||
- Optional newline chunking: set `channels.whatsapp.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- Inbound media saves are capped by `channels.whatsapp.mediaMaxMb` (default 50 MB).
|
||||
- Outbound media items are capped by `agents.defaults.mediaMaxMb` (default 5 MB).
|
||||
|
||||
## Outbound send (text + media)
|
||||
- Uses active web listener; error if gateway not running.
|
||||
- Text chunking: 4k max per message (configurable via `channels.whatsapp.textChunkLimit`, optional `channels.whatsapp.chunkMode`).
|
||||
- Media:
|
||||
- Image/video/audio/document supported.
|
||||
- Audio sent as PTT; `audio/ogg` => `audio/ogg; codecs=opus`.
|
||||
- Caption only on first media item.
|
||||
- Media fetch supports HTTP(S) and local paths.
|
||||
- Animated GIFs: WhatsApp expects MP4 with `gifPlayback: true` for inline looping.
|
||||
- CLI: `bot message send --media <mp4> --gif-playback`
|
||||
- Gateway: `send` params include `gifPlayback: true`
|
||||
|
||||
## Voice notes (PTT audio)
|
||||
WhatsApp sends audio as **voice notes** (PTT bubble).
|
||||
- Best results: OGG/Opus. Bot rewrites `audio/ogg` to `audio/ogg; codecs=opus`.
|
||||
- `[[audio_as_voice]]` is ignored for WhatsApp (audio already ships as voice note).
|
||||
|
||||
## Media limits + optimization
|
||||
- Default outbound cap: 5 MB (per media item).
|
||||
- Override: `agents.defaults.mediaMaxMb`.
|
||||
- Images are auto-optimized to JPEG under cap (resize + quality sweep).
|
||||
- Oversize media => error; media reply falls back to text warning.
|
||||
|
||||
## Heartbeats
|
||||
- **Gateway heartbeat** logs connection health (`web.heartbeatSeconds`, default 60s).
|
||||
- **Agent heartbeat** can be configured per agent (`agents.list[].heartbeat`) or globally
|
||||
via `agents.defaults.heartbeat` (fallback when no per-agent entries are set).
|
||||
- Uses the configured heartbeat prompt (default: `Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.`) + `HEARTBEAT_OK` skip behavior.
|
||||
- Delivery defaults to the last used channel (or configured target).
|
||||
|
||||
## Reconnect behavior
|
||||
- Backoff policy: `web.reconnect`:
|
||||
- `initialMs`, `maxMs`, `factor`, `jitter`, `maxAttempts`.
|
||||
- If maxAttempts reached, web monitoring stops (degraded).
|
||||
- Logged-out => stop and require re-link.
|
||||
|
||||
## Config quick map
|
||||
- `channels.whatsapp.dmPolicy` (DM policy: pairing/allowlist/open/disabled).
|
||||
- `channels.whatsapp.selfChatMode` (same-phone setup; bot uses your personal WhatsApp number).
|
||||
- `channels.whatsapp.allowFrom` (DM allowlist). WhatsApp uses E.164 phone numbers (no usernames).
|
||||
- `channels.whatsapp.mediaMaxMb` (inbound media save cap).
|
||||
- `channels.whatsapp.ackReaction` (auto-reaction on message receipt: `{emoji, direct, group}`).
|
||||
- `channels.whatsapp.accounts.<accountId>.*` (per-account settings + optional `authDir`).
|
||||
- `channels.whatsapp.accounts.<accountId>.mediaMaxMb` (per-account inbound media cap).
|
||||
- `channels.whatsapp.accounts.<accountId>.ackReaction` (per-account ack reaction override).
|
||||
- `channels.whatsapp.groupAllowFrom` (group sender allowlist).
|
||||
- `channels.whatsapp.groupPolicy` (group policy).
|
||||
- `channels.whatsapp.historyLimit` / `channels.whatsapp.accounts.<accountId>.historyLimit` (group history context; `0` disables).
|
||||
- `channels.whatsapp.dmHistoryLimit` (DM history limit in user turns). Per-user overrides: `channels.whatsapp.dms["<phone>"].historyLimit`.
|
||||
- `channels.whatsapp.groups` (group allowlist + mention gating defaults; use `"*"` to allow all)
|
||||
- `channels.whatsapp.actions.reactions` (gate WhatsApp tool reactions).
|
||||
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`)
|
||||
- `messages.groupChat.historyLimit`
|
||||
- `channels.whatsapp.messagePrefix` (inbound prefix; per-account: `channels.whatsapp.accounts.<accountId>.messagePrefix`; deprecated: `messages.messagePrefix`)
|
||||
- `messages.responsePrefix` (outbound prefix)
|
||||
- `agents.defaults.mediaMaxMb`
|
||||
- `agents.defaults.heartbeat.every`
|
||||
- `agents.defaults.heartbeat.model` (optional override)
|
||||
- `agents.defaults.heartbeat.target`
|
||||
- `agents.defaults.heartbeat.to`
|
||||
- `agents.defaults.heartbeat.session`
|
||||
- `agents.list[].heartbeat.*` (per-agent overrides)
|
||||
- `session.*` (scope, idle, store, mainKey)
|
||||
- `web.enabled` (disable channel startup when false)
|
||||
- `web.heartbeatSeconds`
|
||||
- `web.reconnect.*`
|
||||
|
||||
## Logs + troubleshooting
|
||||
- Subsystems: `whatsapp/inbound`, `whatsapp/outbound`, `web-heartbeat`, `web-reconnect`.
|
||||
- Log file: `/tmp/bot/bot-YYYY-MM-DD.log` (configurable).
|
||||
- Troubleshooting guide: [Gateway troubleshooting](/gateway/troubleshooting).
|
||||
|
||||
## Troubleshooting (quick)
|
||||
|
||||
**Not linked / QR login required**
|
||||
- Symptom: `channels status` shows `linked: false` or warns “Not linked”.
|
||||
- Fix: run `bot channels login` on the gateway host and scan the QR (WhatsApp → Settings → Linked Devices).
|
||||
|
||||
**Linked but disconnected / reconnect loop**
|
||||
- Symptom: `channels status` shows `running, disconnected` or warns “Linked but disconnected”.
|
||||
- Fix: `bot doctor` (or restart the gateway). If it persists, relink via `channels login` and inspect `bot logs --follow`.
|
||||
|
||||
**Bun runtime**
|
||||
- Bun is **not recommended**. WhatsApp (Baileys) and Telegram are unreliable on Bun.
|
||||
Run the gateway with **Node**. (See Getting Started runtime note.)
|
||||
@@ -0,0 +1,165 @@
|
||||
---
|
||||
title: "Zalo (Bot API)"
|
||||
description: "Zalo bot support status, capabilities, and configuration"
|
||||
---
|
||||
|
||||
Status: experimental. Direct messages only; groups coming soon per Zalo docs.
|
||||
|
||||
## Plugin required
|
||||
Zalo ships as a plugin and is not bundled with the core install.
|
||||
- Install via CLI: `bot plugins install @bot/zalo`
|
||||
- Or select **Zalo** during onboarding and confirm the install prompt
|
||||
- Details: [Plugins](/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Install the Zalo plugin:
|
||||
- From a source checkout: `bot plugins install ./extensions/zalo`
|
||||
- From npm (if published): `bot plugins install @bot/zalo`
|
||||
- Or pick **Zalo** in onboarding and confirm the install prompt
|
||||
2) Set the token:
|
||||
- Env: `ZALO_BOT_TOKEN=...`
|
||||
- Or config: `channels.zalo.botToken: "..."`.
|
||||
3) Restart the gateway (or finish onboarding).
|
||||
4) DM access is pairing by default; approve the pairing code on first contact.
|
||||
|
||||
Minimal config:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalo: {
|
||||
enabled: true,
|
||||
botToken: "12345689:abc-xyz",
|
||||
dmPolicy: "pairing"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## What it is
|
||||
Zalo is a Vietnam-focused messaging app; its Bot API lets the Gateway run a bot for 1:1 conversations.
|
||||
It is a good fit for support or notifications where you want deterministic routing back to Zalo.
|
||||
- A Zalo Bot API channel owned by the Gateway.
|
||||
- Deterministic routing: replies go back to Zalo; the model never chooses channels.
|
||||
- DMs share the agent's main session.
|
||||
- Groups are not yet supported (Zalo docs state "coming soon").
|
||||
|
||||
## Setup (fast path)
|
||||
|
||||
### 1) Create a bot token (Zalo Bot Platform)
|
||||
1) Go to **https://bot.zaloplatforms.com** and sign in.
|
||||
2) Create a new bot and configure its settings.
|
||||
3) Copy the bot token (format: `12345689:abc-xyz`).
|
||||
|
||||
### 2) Configure the token (env or config)
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalo: {
|
||||
enabled: true,
|
||||
botToken: "12345689:abc-xyz",
|
||||
dmPolicy: "pairing"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Env option: `ZALO_BOT_TOKEN=...` (works for the default account only).
|
||||
|
||||
Multi-account support: use `channels.zalo.accounts` with per-account tokens and optional `name`.
|
||||
|
||||
3) Restart the gateway. Zalo starts when a token is resolved (env or config).
|
||||
4) DM access defaults to pairing. Approve the code when the bot is first contacted.
|
||||
|
||||
## How it works (behavior)
|
||||
- Inbound messages are normalized into the shared channel envelope with media placeholders.
|
||||
- Replies always route back to the same Zalo chat.
|
||||
- Long-polling by default; webhook mode available with `channels.zalo.webhookUrl`.
|
||||
|
||||
## Limits
|
||||
- Outbound text is chunked to 2000 characters (Zalo API limit).
|
||||
- Media downloads/uploads are capped by `channels.zalo.mediaMaxMb` (default 5).
|
||||
- Streaming is blocked by default due to the 2000 char limit making streaming less useful.
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
### DM access
|
||||
- Default: `channels.zalo.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
- `bot pairing list zalo`
|
||||
- `bot pairing approve zalo <CODE>`
|
||||
- Pairing is the default token exchange. Details: [Pairing](/start/pairing)
|
||||
- `channels.zalo.allowFrom` accepts numeric user IDs (no username lookup available).
|
||||
|
||||
## Long-polling vs webhook
|
||||
- Default: long-polling (no public URL required).
|
||||
- Webhook mode: set `channels.zalo.webhookUrl` and `channels.zalo.webhookSecret`.
|
||||
- The webhook secret must be 8-256 characters.
|
||||
- Webhook URL must use HTTPS.
|
||||
- Zalo sends events with `X-Bot-Api-Secret-Token` header for verification.
|
||||
- Gateway HTTP handles webhook requests at `channels.zalo.webhookPath` (defaults to the webhook URL path).
|
||||
|
||||
**Note:** getUpdates (polling) and webhook are mutually exclusive per Zalo API docs.
|
||||
|
||||
## Supported message types
|
||||
- **Text messages**: Full support with 2000 character chunking.
|
||||
- **Image messages**: Download and process inbound images; send images via `sendPhoto`.
|
||||
- **Stickers**: Logged but not fully processed (no agent response).
|
||||
- **Unsupported types**: Logged (e.g., messages from protected users).
|
||||
|
||||
## Capabilities
|
||||
| Feature | Status |
|
||||
|---------|--------|
|
||||
| Direct messages | ✅ Supported |
|
||||
| Groups | ❌ Coming soon (per Zalo docs) |
|
||||
| Media (images) | ✅ Supported |
|
||||
| Reactions | ❌ Not supported |
|
||||
| Threads | ❌ Not supported |
|
||||
| Polls | ❌ Not supported |
|
||||
| Native commands | ❌ Not supported |
|
||||
| Streaming | ⚠️ Blocked (2000 char limit) |
|
||||
|
||||
## Delivery targets (CLI/cron)
|
||||
- Use a chat id as the target.
|
||||
- Example: `bot message send --channel zalo --target 123456789 --message "hi"`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Bot doesn't respond:**
|
||||
- Check that the token is valid: `bot channels status --probe`
|
||||
- Verify the sender is approved (pairing or allowFrom)
|
||||
- Check gateway logs: `bot logs --follow`
|
||||
|
||||
**Webhook not receiving events:**
|
||||
- Ensure webhook URL uses HTTPS
|
||||
- Verify secret token is 8-256 characters
|
||||
- Confirm the gateway HTTP endpoint is reachable on the configured path
|
||||
- Check that getUpdates polling is not running (they're mutually exclusive)
|
||||
|
||||
## Configuration reference (Zalo)
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
- `channels.zalo.enabled`: enable/disable channel startup.
|
||||
- `channels.zalo.botToken`: bot token from Zalo Bot Platform.
|
||||
- `channels.zalo.tokenFile`: read token from file path.
|
||||
- `channels.zalo.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing).
|
||||
- `channels.zalo.allowFrom`: DM allowlist (user IDs). `open` requires `"*"`. The wizard will ask for numeric IDs.
|
||||
- `channels.zalo.mediaMaxMb`: inbound/outbound media cap (MB, default 5).
|
||||
- `channels.zalo.webhookUrl`: enable webhook mode (HTTPS required).
|
||||
- `channels.zalo.webhookSecret`: webhook secret (8-256 chars).
|
||||
- `channels.zalo.webhookPath`: webhook path on the gateway HTTP server.
|
||||
- `channels.zalo.proxy`: proxy URL for API requests.
|
||||
|
||||
Multi-account options:
|
||||
- `channels.zalo.accounts.<id>.botToken`: per-account token.
|
||||
- `channels.zalo.accounts.<id>.tokenFile`: per-account token file.
|
||||
- `channels.zalo.accounts.<id>.name`: display name.
|
||||
- `channels.zalo.accounts.<id>.enabled`: enable/disable account.
|
||||
- `channels.zalo.accounts.<id>.dmPolicy`: per-account DM policy.
|
||||
- `channels.zalo.accounts.<id>.allowFrom`: per-account allowlist.
|
||||
- `channels.zalo.accounts.<id>.webhookUrl`: per-account webhook URL.
|
||||
- `channels.zalo.accounts.<id>.webhookSecret`: per-account webhook secret.
|
||||
- `channels.zalo.accounts.<id>.webhookPath`: per-account webhook path.
|
||||
- `channels.zalo.accounts.<id>.proxy`: per-account proxy URL.
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
title: "Zalo Personal (unofficial)"
|
||||
description: "Zalo personal account support via zca-cli (QR login), capabilities, and configuration"
|
||||
---
|
||||
|
||||
Status: experimental. This integration automates a **personal Zalo account** via `zca-cli`.
|
||||
|
||||
> **Warning:** This is an unofficial integration and may result in account suspension/ban. Use at your own risk.
|
||||
|
||||
## Plugin required
|
||||
Zalo Personal ships as a plugin and is not bundled with the core install.
|
||||
- Install via CLI: `bot plugins install @bot/zalouser`
|
||||
- Or from a source checkout: `bot plugins install ./extensions/zalouser`
|
||||
- Details: [Plugins](/plugin)
|
||||
|
||||
## Prerequisite: zca-cli
|
||||
The Gateway machine must have the `zca` binary available in `PATH`.
|
||||
|
||||
- Verify: `zca --version`
|
||||
- If missing, install zca-cli (see `extensions/zalouser/README.md` or the upstream zca-cli docs).
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Install the plugin (see above).
|
||||
2) Login (QR, on the Gateway machine):
|
||||
- `bot channels login --channel zalouser`
|
||||
- Scan the QR code in the terminal with the Zalo mobile app.
|
||||
3) Enable the channel:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalouser: {
|
||||
enabled: true,
|
||||
dmPolicy: "pairing"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4) Restart the Gateway (or finish onboarding).
|
||||
5) DM access defaults to pairing; approve the pairing code on first contact.
|
||||
|
||||
## What it is
|
||||
- Uses `zca listen` to receive inbound messages.
|
||||
- Uses `zca msg ...` to send replies (text/media/link).
|
||||
- Designed for “personal account” use cases where Zalo Bot API is not available.
|
||||
|
||||
## Naming
|
||||
Channel id is `zalouser` to make it explicit this automates a **personal Zalo user account** (unofficial). We keep `zalo` reserved for a potential future official Zalo API integration.
|
||||
|
||||
## Finding IDs (directory)
|
||||
Use the directory CLI to discover peers/groups and their IDs:
|
||||
|
||||
```bash
|
||||
bot directory self --channel zalouser
|
||||
bot directory peers list --channel zalouser --query "name"
|
||||
bot directory groups list --channel zalouser --query "work"
|
||||
```
|
||||
|
||||
## Limits
|
||||
- Outbound text is chunked to ~2000 characters (Zalo client limits).
|
||||
- Streaming is blocked by default.
|
||||
|
||||
## Access control (DMs)
|
||||
`channels.zalouser.dmPolicy` supports: `pairing | allowlist | open | disabled` (default: `pairing`).
|
||||
`channels.zalouser.allowFrom` accepts user IDs or names. The wizard resolves names to IDs via `zca friend find` when available.
|
||||
|
||||
Approve via:
|
||||
- `bot pairing list zalouser`
|
||||
- `bot pairing approve zalouser <code>`
|
||||
|
||||
## Group access (optional)
|
||||
- Default: `channels.zalouser.groupPolicy = "open"` (groups allowed). Use `channels.defaults.groupPolicy` to override the default when unset.
|
||||
- Restrict to an allowlist with:
|
||||
- `channels.zalouser.groupPolicy = "allowlist"`
|
||||
- `channels.zalouser.groups` (keys are group IDs or names)
|
||||
- Block all groups: `channels.zalouser.groupPolicy = "disabled"`.
|
||||
- The configure wizard can prompt for group allowlists.
|
||||
- On startup, Bot resolves group/user names in allowlists to IDs and logs the mapping; unresolved entries are kept as typed.
|
||||
|
||||
Example:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalouser: {
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
"123456789": { allow: true },
|
||||
"Work Chat": { allow: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Multi-account
|
||||
Accounts map to zca profiles. Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalouser: {
|
||||
enabled: true,
|
||||
defaultAccount: "default",
|
||||
accounts: {
|
||||
work: { enabled: true, profile: "work" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**`zca` not found:**
|
||||
- Install zca-cli and ensure it’s on `PATH` for the Gateway process.
|
||||
|
||||
**Login doesn’t stick:**
|
||||
- `bot channels status --probe`
|
||||
- Re-login: `bot channels logout --channel zalouser && bot channels login --channel zalouser`
|
||||
@@ -0,0 +1,162 @@
|
||||
---
|
||||
title: "acp"
|
||||
description: "Run the ACP bridge for IDE integrations"
|
||||
---
|
||||
|
||||
Run the ACP (Agent Client Protocol) bridge that talks to a Bot Gateway.
|
||||
|
||||
This command speaks ACP over stdio for IDEs and forwards prompts to the Gateway
|
||||
over WebSocket. It keeps ACP sessions mapped to Gateway session keys.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
bot acp
|
||||
|
||||
# Remote Gateway
|
||||
bot acp --url wss://gateway-host:18789 --token <token>
|
||||
|
||||
# Attach to an existing session key
|
||||
bot acp --session agent:main:main
|
||||
|
||||
# Attach by label (must already exist)
|
||||
bot acp --session-label "support inbox"
|
||||
|
||||
# Reset the session key before the first prompt
|
||||
bot acp --session agent:main:main --reset-session
|
||||
```
|
||||
|
||||
## ACP client (debug)
|
||||
|
||||
Use the built-in ACP client to sanity-check the bridge without an IDE.
|
||||
It spawns the ACP bridge and lets you type prompts interactively.
|
||||
|
||||
```bash
|
||||
bot acp client
|
||||
|
||||
# Point the spawned bridge at a remote Gateway
|
||||
bot acp client --server-args --url wss://gateway-host:18789 --token <token>
|
||||
|
||||
# Override the server command (default: bot)
|
||||
bot acp client --server "node" --server-args dist/entry.js acp --url ws://127.0.0.1:19001
|
||||
```
|
||||
|
||||
## How to use this
|
||||
|
||||
Use ACP when an IDE (or other client) speaks Agent Client Protocol and you want
|
||||
it to drive a Bot Gateway session.
|
||||
|
||||
1. Ensure the Gateway is running (local or remote).
|
||||
2. Configure the Gateway target (config or flags).
|
||||
3. Point your IDE to run `bot acp` over stdio.
|
||||
|
||||
Example config (persisted):
|
||||
|
||||
```bash
|
||||
bot config set gateway.remote.url wss://gateway-host:18789
|
||||
bot config set gateway.remote.token <token>
|
||||
```
|
||||
|
||||
Example direct run (no config write):
|
||||
|
||||
```bash
|
||||
bot acp --url wss://gateway-host:18789 --token <token>
|
||||
```
|
||||
|
||||
## Selecting agents
|
||||
|
||||
ACP does not pick agents directly. It routes by the Gateway session key.
|
||||
|
||||
Use agent-scoped session keys to target a specific agent:
|
||||
|
||||
```bash
|
||||
bot acp --session agent:main:main
|
||||
bot acp --session agent:design:main
|
||||
bot acp --session agent:qa:bug-123
|
||||
```
|
||||
|
||||
Each ACP session maps to a single Gateway session key. One agent can have many
|
||||
sessions; ACP defaults to an isolated `acp:<uuid>` session unless you override
|
||||
the key or label.
|
||||
|
||||
## Zed editor setup
|
||||
|
||||
Add a custom ACP agent in `~/.config/zed/settings.json` (or use Zed’s Settings UI):
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"Bot ACP": {
|
||||
"type": "custom",
|
||||
"command": "bot",
|
||||
"args": ["acp"],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To target a specific Gateway or agent:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"Bot ACP": {
|
||||
"type": "custom",
|
||||
"command": "bot",
|
||||
"args": [
|
||||
"acp",
|
||||
"--url", "wss://gateway-host:18789",
|
||||
"--token", "<token>",
|
||||
"--session", "agent:design:main"
|
||||
],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In Zed, open the Agent panel and select “Bot ACP” to start a thread.
|
||||
|
||||
## Session mapping
|
||||
|
||||
By default, ACP sessions get an isolated Gateway session key with an `acp:` prefix.
|
||||
To reuse a known session, pass a session key or label:
|
||||
|
||||
- `--session <key>`: use a specific Gateway session key.
|
||||
- `--session-label <label>`: resolve an existing session by label.
|
||||
- `--reset-session`: mint a fresh session id for that key (same key, new transcript).
|
||||
|
||||
If your ACP client supports metadata, you can override per session:
|
||||
|
||||
```json
|
||||
{
|
||||
"_meta": {
|
||||
"sessionKey": "agent:main:main",
|
||||
"sessionLabel": "support inbox",
|
||||
"resetSession": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Learn more about session keys at [/concepts/session](/concepts/session).
|
||||
|
||||
## Options
|
||||
|
||||
- `--url <url>`: Gateway WebSocket URL (defaults to gateway.remote.url when configured).
|
||||
- `--token <token>`: Gateway auth token.
|
||||
- `--password <password>`: Gateway auth password.
|
||||
- `--session <key>`: default session key.
|
||||
- `--session-label <label>`: default session label to resolve.
|
||||
- `--require-existing`: fail if the session key/label does not exist.
|
||||
- `--reset-session`: reset the session key before first use.
|
||||
- `--no-prefix-cwd`: do not prefix prompts with the working directory.
|
||||
- `--verbose, -v`: verbose logging to stderr.
|
||||
|
||||
### `acp client` options
|
||||
|
||||
- `--cwd <dir>`: working directory for the ACP session.
|
||||
- `--server <command>`: ACP server command (default: `bot`).
|
||||
- `--server-args <args...>`: extra arguments passed to the ACP server.
|
||||
- `--server-verbose`: enable verbose logging on the ACP server.
|
||||
- `--verbose, -v`: verbose client logging.
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: "`bot agent`"
|
||||
description: "CLI reference for `bot agent` (send one agent turn via the Gateway)"
|
||||
---
|
||||
|
||||
Run an agent turn via the Gateway (use `--local` for embedded).
|
||||
Use `--agent <id>` to target a configured agent directly.
|
||||
|
||||
Related:
|
||||
- Agent send tool: [Agent send](/tools/agent-send)
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
bot agent --to +15555550123 --message "status update" --deliver
|
||||
bot agent --agent ops --message "Summarize logs"
|
||||
bot agent --session-id 1234 --message "Summarize inbox" --thinking medium
|
||||
bot agent --agent ops --message "Generate report" --deliver --reply-channel slack --reply-to "#reports"
|
||||
```
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: "`bot agents`"
|
||||
description: "CLI reference for `bot agents` (list/add/delete/set identity)"
|
||||
---
|
||||
|
||||
Manage isolated agents (workspaces + auth + routing).
|
||||
|
||||
Related:
|
||||
- Multi-agent routing: [Multi-Agent Routing](/concepts/multi-agent)
|
||||
- Agent workspace: [Agent workspace](/concepts/agent-workspace)
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
bot agents list
|
||||
bot agents add work --workspace ~/bot-work
|
||||
bot agents set-identity --workspace ~/bot --from-identity
|
||||
bot agents set-identity --agent main --avatar avatars/bot.png
|
||||
bot agents delete work
|
||||
```
|
||||
|
||||
## Identity files
|
||||
|
||||
Each agent workspace can include an `IDENTITY.md` at the workspace root:
|
||||
- Example path: `~/bot/IDENTITY.md`
|
||||
- `set-identity --from-identity` reads from the workspace root (or an explicit `--identity-file`)
|
||||
|
||||
Avatar paths resolve relative to the workspace root.
|
||||
|
||||
## Set identity
|
||||
|
||||
`set-identity` writes fields into `agents.list[].identity`:
|
||||
- `name`
|
||||
- `theme`
|
||||
- `emoji`
|
||||
- `avatar` (workspace-relative path, http(s) URL, or data URI)
|
||||
|
||||
Load from `IDENTITY.md`:
|
||||
|
||||
```bash
|
||||
bot agents set-identity --workspace ~/bot --from-identity
|
||||
```
|
||||
|
||||
Override fields explicitly:
|
||||
|
||||
```bash
|
||||
bot agents set-identity --agent main --name "Clawd" --emoji "🦞" --avatar avatars/bot.png
|
||||
```
|
||||
|
||||
Config sample:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
identity: {
|
||||
name: "Clawd",
|
||||
theme: "space lobster",
|
||||
emoji: "🦞",
|
||||
avatar: "avatars/bot.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "`bot approvals`"
|
||||
description: "CLI reference for `bot approvals` (exec approvals for gateway or node hosts)"
|
||||
---
|
||||
|
||||
Manage exec approvals for the **local host**, **gateway host**, or a **node host**.
|
||||
By default, commands target the local approvals file on disk. Use `--gateway` to target the gateway, or `--node` to target a specific node.
|
||||
|
||||
Related:
|
||||
- Exec approvals: [Exec approvals](/tools/exec-approvals)
|
||||
- Nodes: [Nodes](/nodes)
|
||||
|
||||
## Common commands
|
||||
|
||||
```bash
|
||||
bot approvals get
|
||||
bot approvals get --node <id|name|ip>
|
||||
bot approvals get --gateway
|
||||
```
|
||||
|
||||
## Replace approvals from a file
|
||||
|
||||
```bash
|
||||
bot approvals set --file ./exec-approvals.json
|
||||
bot approvals set --node <id|name|ip> --file ./exec-approvals.json
|
||||
bot approvals set --gateway --file ./exec-approvals.json
|
||||
```
|
||||
|
||||
## Allowlist helpers
|
||||
|
||||
```bash
|
||||
bot approvals allowlist add "~/Projects/**/bin/rg"
|
||||
bot approvals allowlist add --agent main --node <id|name|ip> "/usr/bin/uptime"
|
||||
bot approvals allowlist add --agent "*" "/usr/bin/uname"
|
||||
|
||||
bot approvals allowlist remove "~/Projects/**/bin/rg"
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `--node` uses the same resolver as `bot nodes` (id, name, ip, or id prefix).
|
||||
- `--agent` defaults to `"*"`, which applies to all agents.
|
||||
- The node host must advertise `system.execApprovals.get/set` (macOS app or headless node host).
|
||||
- Approvals files are stored per host at `~/.bot/exec-approvals.json`.
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
title: "`bot browser`"
|
||||
description: "CLI reference for `bot browser` (profiles, tabs, actions, extension relay, remote serve)"
|
||||
---
|
||||
|
||||
Manage Bot’s browser control server and run browser actions (tabs, snapshots, screenshots, navigation, clicks, typing).
|
||||
|
||||
Related:
|
||||
- Browser tool + API: [Browser tool](/tools/browser)
|
||||
- Chrome extension relay: [Chrome extension](/tools/chrome-extension)
|
||||
|
||||
## Common flags
|
||||
|
||||
- `--url <controlUrl>`: override `browser.controlUrl` for this command invocation.
|
||||
- `--browser-profile <name>`: choose a browser profile (default comes from config).
|
||||
- `--json`: machine-readable output (where supported).
|
||||
|
||||
## Quick start (local)
|
||||
|
||||
```bash
|
||||
bot browser --browser-profile chrome tabs
|
||||
bot browser --browser-profile bot start
|
||||
bot browser --browser-profile bot open https://example.com
|
||||
bot browser --browser-profile bot snapshot
|
||||
```
|
||||
|
||||
## Profiles
|
||||
|
||||
Profiles are named browser routing configs. In practice:
|
||||
- `bot`: launches/attaches to a dedicated Bot-managed Chrome instance (isolated user data dir).
|
||||
- `chrome`: controls your existing Chrome tab(s) via the Chrome extension relay.
|
||||
|
||||
```bash
|
||||
bot browser profiles
|
||||
bot browser create-profile --name work --color "#FF5A36"
|
||||
bot browser delete-profile --name work
|
||||
```
|
||||
|
||||
Use a specific profile:
|
||||
|
||||
```bash
|
||||
bot browser --browser-profile work tabs
|
||||
```
|
||||
|
||||
## Tabs
|
||||
|
||||
```bash
|
||||
bot browser tabs
|
||||
bot browser open https://docs.hanzo.bot
|
||||
bot browser focus <targetId>
|
||||
bot browser close <targetId>
|
||||
```
|
||||
|
||||
## Snapshot / screenshot / actions
|
||||
|
||||
Snapshot:
|
||||
|
||||
```bash
|
||||
bot browser snapshot
|
||||
```
|
||||
|
||||
Screenshot:
|
||||
|
||||
```bash
|
||||
bot browser screenshot
|
||||
```
|
||||
|
||||
Navigate/click/type (ref-based UI automation):
|
||||
|
||||
```bash
|
||||
bot browser navigate https://example.com
|
||||
bot browser click <ref>
|
||||
bot browser type <ref> "hello"
|
||||
```
|
||||
|
||||
## Chrome extension relay (attach via toolbar button)
|
||||
|
||||
This mode lets the agent control an existing Chrome tab that you attach manually (it does not auto-attach).
|
||||
|
||||
Install the unpacked extension to a stable path:
|
||||
|
||||
```bash
|
||||
bot browser extension install
|
||||
bot browser extension path
|
||||
```
|
||||
|
||||
Then Chrome → `chrome://extensions` → enable “Developer mode” → “Load unpacked” → select the printed folder.
|
||||
|
||||
Full guide: [Chrome extension](/tools/chrome-extension)
|
||||
|
||||
## Remote browser control (`bot browser serve`)
|
||||
|
||||
If the Gateway runs on a different machine than the browser, run a standalone browser control server on the machine that runs Chrome:
|
||||
|
||||
```bash
|
||||
bot browser serve --bind 127.0.0.1 --port 18791 --token <token>
|
||||
```
|
||||
|
||||
Then point the Gateway at it using `browser.controlUrl` + `browser.controlToken` (or `BOT_BROWSER_CONTROL_TOKEN`).
|
||||
|
||||
Security + TLS best-practices: [Browser tool](/tools/browser), [Tailscale](/gateway/tailscale), [Security](/gateway/security)
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: "`bot channels`"
|
||||
description: "CLI reference for `bot channels` (accounts, status, login/logout, logs)"
|
||||
---
|
||||
|
||||
Manage chat channel accounts and their runtime status on the Gateway.
|
||||
|
||||
Related docs:
|
||||
- Channel guides: [Channels](/channels/index)
|
||||
- Gateway configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
## Common commands
|
||||
|
||||
```bash
|
||||
bot channels list
|
||||
bot channels status
|
||||
bot channels capabilities
|
||||
bot channels capabilities --channel discord --target channel:123
|
||||
bot channels resolve --channel slack "#general" "@jane"
|
||||
bot channels logs --channel all
|
||||
```
|
||||
|
||||
## Add / remove accounts
|
||||
|
||||
```bash
|
||||
bot channels add --channel telegram --token <bot-token>
|
||||
bot channels remove --channel telegram --delete
|
||||
```
|
||||
|
||||
Tip: `bot channels add --help` shows per-channel flags (token, app token, signal-cli paths, etc).
|
||||
|
||||
## Login / logout (interactive)
|
||||
|
||||
```bash
|
||||
bot channels login --channel whatsapp
|
||||
bot channels logout --channel whatsapp
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Run `bot status --deep` for a broad probe.
|
||||
- Use `bot doctor` for guided fixes.
|
||||
- `bot channels list` prints `Claude: HTTP 403 ... user:profile` → usage snapshot needs the `user:profile` scope. Use `--no-usage`, or provide a claude.ai session key (`CLAUDE_WEB_SESSION_KEY` / `CLAUDE_WEB_COOKIE`), or re-auth via Claude Code CLI.
|
||||
|
||||
## Capabilities probe
|
||||
|
||||
Fetch provider capability hints (intents/scopes where available) plus static feature support:
|
||||
|
||||
```bash
|
||||
bot channels capabilities
|
||||
bot channels capabilities --channel discord --target channel:123
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `--channel` is optional; omit it to list every channel (including extensions).
|
||||
- `--target` accepts `channel:<id>` or a raw numeric channel id and only applies to Discord.
|
||||
- Probes are provider-specific: Discord intents + optional channel permissions; Slack bot + user scopes; Telegram bot flags + webhook; Signal daemon version; MS Teams app token + Graph roles/scopes (annotated where known). Channels without probes report `Probe: unavailable`.
|
||||
|
||||
## Resolve names to IDs
|
||||
|
||||
Resolve channel/user names to IDs using the provider directory:
|
||||
|
||||
```bash
|
||||
bot channels resolve --channel slack "#general" "@jane"
|
||||
bot channels resolve --channel discord "My Server/#support" "@someone"
|
||||
bot channels resolve --channel matrix "Project Room"
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Use `--kind user|group|auto` to force the target type.
|
||||
- Resolution prefers active matches when multiple entries share the same name.
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: "`bot config`"
|
||||
description: "CLI reference for `bot config` (get/set/unset config values)"
|
||||
---
|
||||
|
||||
Config helpers: get/set/unset values by path. Run without a subcommand to open
|
||||
the configure wizard (same as `bot configure`).
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
bot config get browser.executablePath
|
||||
bot config set browser.executablePath "/usr/bin/google-chrome"
|
||||
bot config set agents.defaults.heartbeat.every "2h"
|
||||
bot config set agents.list[0].tools.exec.node "node-id-or-name"
|
||||
bot config unset tools.web.search.apiKey
|
||||
```
|
||||
|
||||
## Paths
|
||||
|
||||
Paths use dot or bracket notation:
|
||||
|
||||
```bash
|
||||
bot config get agents.defaults.workspace
|
||||
bot config get agents.list[0].id
|
||||
```
|
||||
|
||||
Use the agent list index to target a specific agent:
|
||||
|
||||
```bash
|
||||
bot config get agents.list
|
||||
bot config set agents.list[1].tools.exec.node "node-id-or-name"
|
||||
```
|
||||
|
||||
## Values
|
||||
|
||||
Values are parsed as JSON5 when possible; otherwise they are treated as strings.
|
||||
Use `--json` to require JSON5 parsing.
|
||||
|
||||
```bash
|
||||
bot config set agents.defaults.heartbeat.every "0m"
|
||||
bot config set gateway.port 19001 --json
|
||||
bot config set channels.whatsapp.groups '["*"]' --json
|
||||
```
|
||||
|
||||
Restart the gateway after edits.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "`bot configure`"
|
||||
description: "CLI reference for `bot configure` (interactive configuration prompts)"
|
||||
---
|
||||
|
||||
Interactive prompt to set up credentials, devices, and agent defaults.
|
||||
|
||||
Note: The **Model** section now includes a multi-select for the
|
||||
`agents.defaults.models` allowlist (what shows up in `/model` and the model picker).
|
||||
|
||||
Tip: `bot config` without a subcommand opens the same wizard. Use
|
||||
`bot config get|set|unset` for non-interactive edits.
|
||||
|
||||
Related:
|
||||
- Gateway configuration reference: [Configuration](/gateway/configuration)
|
||||
- Config CLI: [Config](/cli/config)
|
||||
|
||||
Notes:
|
||||
- Choosing where the Gateway runs always updates `gateway.mode`. You can select "Continue" without other sections if that is all you need.
|
||||
- Channel-oriented services (Slack/Discord/Matrix/Microsoft Teams) prompt for channel/room allowlists during setup. You can enter names or IDs; the wizard resolves names to IDs when possible.
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
bot configure
|
||||
bot configure --section models --section channels
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: "`bot cron`"
|
||||
description: "CLI reference for `bot cron` (schedule and run background jobs)"
|
||||
---
|
||||
|
||||
Manage cron jobs for the Gateway scheduler.
|
||||
|
||||
Related:
|
||||
- Cron jobs: [Cron jobs](/automation/cron-jobs)
|
||||
|
||||
Tip: run `bot cron --help` for the full command surface.
|
||||
|
||||
## Common edits
|
||||
|
||||
Update delivery settings without changing the message:
|
||||
|
||||
```bash
|
||||
bot cron edit <job-id> --deliver --channel telegram --to "123456789"
|
||||
```
|
||||
|
||||
Disable delivery for an isolated job:
|
||||
|
||||
```bash
|
||||
bot cron edit <job-id> --no-deliver
|
||||
```
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "`bot dashboard`"
|
||||
description: "CLI reference for `bot dashboard` (open the Control UI)"
|
||||
---
|
||||
|
||||
Open the Control UI using your current auth.
|
||||
|
||||
```bash
|
||||
bot dashboard
|
||||
bot dashboard --no-open
|
||||
```
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: "`bot devices`"
|
||||
description: "CLI reference for `bot devices` (device pairing + token rotation/revocation)"
|
||||
---
|
||||
|
||||
Manage device pairing requests and device-scoped tokens.
|
||||
|
||||
## Commands
|
||||
|
||||
### `bot devices list`
|
||||
|
||||
List pending pairing requests and paired devices.
|
||||
|
||||
```
|
||||
bot devices list
|
||||
bot devices list --json
|
||||
```
|
||||
|
||||
### `bot devices approve <requestId>`
|
||||
|
||||
Approve a pending device pairing request.
|
||||
|
||||
```
|
||||
bot devices approve <requestId>
|
||||
```
|
||||
|
||||
### `bot devices reject <requestId>`
|
||||
|
||||
Reject a pending device pairing request.
|
||||
|
||||
```
|
||||
bot devices reject <requestId>
|
||||
```
|
||||
|
||||
### `bot devices rotate --device <id> --role <role> [--scope <scope...>]`
|
||||
|
||||
Rotate a device token for a specific role (optionally updating scopes).
|
||||
|
||||
```
|
||||
bot devices rotate --device <deviceId> --role operator --scope operator.read --scope operator.write
|
||||
```
|
||||
|
||||
### `bot devices revoke --device <id> --role <role>`
|
||||
|
||||
Revoke a device token for a specific role.
|
||||
|
||||
```
|
||||
bot devices revoke --device <deviceId> --role node
|
||||
```
|
||||
|
||||
## Common options
|
||||
|
||||
- `--url <url>`: Gateway WebSocket URL (defaults to `gateway.remote.url` when configured).
|
||||
- `--token <token>`: Gateway token (if required).
|
||||
- `--password <password>`: Gateway password (password auth).
|
||||
- `--timeout <ms>`: RPC timeout.
|
||||
- `--json`: JSON output (recommended for scripting).
|
||||
|
||||
## Notes
|
||||
|
||||
- Token rotation returns a new token (sensitive). Treat it like a secret.
|
||||
- These commands require `operator.pairing` (or `operator.admin`) scope.
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: "`bot directory`"
|
||||
description: "CLI reference for `bot directory` (self, peers, groups)"
|
||||
---
|
||||
|
||||
Directory lookups for channels that support it (contacts/peers, groups, and “me”).
|
||||
|
||||
## Common flags
|
||||
- `--channel <name>`: channel id/alias (required when multiple channels are configured; auto when only one is configured)
|
||||
- `--account <id>`: account id (default: channel default)
|
||||
- `--json`: output JSON
|
||||
|
||||
## Notes
|
||||
- `directory` is meant to help you find IDs you can paste into other commands (especially `bot message send --target ...`).
|
||||
- For many channels, results are config-backed (allowlists / configured groups) rather than a live provider directory.
|
||||
- Default output is `id` (and sometimes `name`) separated by a tab; use `--json` for scripting.
|
||||
|
||||
## Using results with `message send`
|
||||
|
||||
```bash
|
||||
bot directory peers list --channel slack --query "U0"
|
||||
bot message send --channel slack --target user:U012ABCDEF --message "hello"
|
||||
```
|
||||
|
||||
## ID formats (by channel)
|
||||
|
||||
- WhatsApp: `+15551234567` (DM), `[email protected]` (group)
|
||||
- Telegram: `@username` or numeric chat id; groups are numeric ids
|
||||
- Slack: `user:U…` and `channel:C…`
|
||||
- Discord: `user:<id>` and `channel:<id>`
|
||||
- Matrix (plugin): `user:@user:server`, `room:!roomId:server`, or `#alias:server`
|
||||
- Microsoft Teams (plugin): `user:<id>` and `conversation:<id>`
|
||||
- Zalo (plugin): user id (Bot API)
|
||||
- Zalo Personal / `zalouser` (plugin): thread id (DM/group) from `zca` (`me`, `friend list`, `group list`)
|
||||
|
||||
## Self (“me”)
|
||||
|
||||
```bash
|
||||
bot directory self --channel zalouser
|
||||
```
|
||||
|
||||
## Peers (contacts/users)
|
||||
|
||||
```bash
|
||||
bot directory peers list --channel zalouser
|
||||
bot directory peers list --channel zalouser --query "name"
|
||||
bot directory peers list --channel zalouser --limit 50
|
||||
```
|
||||
|
||||
## Groups
|
||||
|
||||
```bash
|
||||
bot directory groups list --channel zalouser
|
||||
bot directory groups list --channel zalouser --query "work"
|
||||
bot directory groups members --channel zalouser --group-id <id>
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: "`bot dns`"
|
||||
description: "CLI reference for `bot dns` (wide-area discovery helpers)"
|
||||
---
|
||||
|
||||
DNS helpers for wide-area discovery (Tailscale + CoreDNS). Currently focused on macOS + Homebrew CoreDNS.
|
||||
|
||||
Related:
|
||||
- Gateway discovery: [Discovery](/gateway/discovery)
|
||||
- Wide-area discovery config: [Configuration](/gateway/configuration)
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
bot dns setup
|
||||
bot dns setup --apply
|
||||
```
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "`bot docs`"
|
||||
description: "CLI reference for `bot docs` (search the live docs index)"
|
||||
---
|
||||
|
||||
Search the live docs index.
|
||||
|
||||
```bash
|
||||
bot docs browser extension
|
||||
bot docs sandbox allowHostControl
|
||||
```
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: "`bot doctor`"
|
||||
description: "CLI reference for `bot doctor` (health checks + guided repairs)"
|
||||
---
|
||||
|
||||
Health checks + quick fixes for the gateway and channels.
|
||||
|
||||
Related:
|
||||
- Troubleshooting: [Troubleshooting](/gateway/troubleshooting)
|
||||
- Security audit: [Security](/gateway/security)
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
bot doctor
|
||||
bot doctor --repair
|
||||
bot doctor --deep
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Interactive prompts (like keychain/OAuth fixes) only run when stdin is a TTY and `--non-interactive` is **not** set. Headless runs (cron, Telegram, no terminal) will skip prompts.
|
||||
- `--fix` (alias for `--repair`) writes a backup to `~/.bot/bot.json.bak` and drops unknown config keys, listing each removal.
|
||||
|
||||
## macOS: `launchctl` env overrides
|
||||
|
||||
If you previously ran `launchctl setenv BOT_GATEWAY_TOKEN ...` (or `...PASSWORD`), that value overrides your config file and can cause persistent “unauthorized” errors.
|
||||
|
||||
```bash
|
||||
launchctl getenv BOT_GATEWAY_TOKEN
|
||||
launchctl getenv BOT_GATEWAY_PASSWORD
|
||||
|
||||
launchctl unsetenv BOT_GATEWAY_TOKEN
|
||||
launchctl unsetenv BOT_GATEWAY_PASSWORD
|
||||
```
|
||||
@@ -0,0 +1,182 @@
|
||||
---
|
||||
title: "Gateway CLI"
|
||||
description: "Bot Gateway CLI (`bot gateway`) — run, query, and discover gateways"
|
||||
---
|
||||
|
||||
The Gateway is Bot’s WebSocket server (channels, nodes, sessions, hooks).
|
||||
|
||||
Subcommands in this page live under `bot gateway …`.
|
||||
|
||||
Related docs:
|
||||
- [/gateway/bonjour](/gateway/bonjour)
|
||||
- [/gateway/discovery](/gateway/discovery)
|
||||
- [/gateway/configuration](/gateway/configuration)
|
||||
|
||||
## Run the Gateway
|
||||
|
||||
Run a local Gateway process:
|
||||
|
||||
```bash
|
||||
bot gateway
|
||||
```
|
||||
|
||||
Foreground alias:
|
||||
|
||||
```bash
|
||||
bot gateway run
|
||||
```
|
||||
|
||||
Notes:
|
||||
- By default, the Gateway refuses to start unless `gateway.mode=local` is set in `~/.bot/bot.json`. Use `--allow-unconfigured` for ad-hoc/dev runs.
|
||||
- Binding beyond loopback without auth is blocked (safety guardrail).
|
||||
- `SIGUSR1` triggers an in-process restart when authorized (enable `commands.restart` or use the gateway tool/config apply/update).
|
||||
- `SIGINT`/`SIGTERM` handlers stop the gateway process, but they don’t restore any custom terminal state. If you wrap the CLI with a TUI or raw-mode input, restore the terminal before exit.
|
||||
|
||||
### Options
|
||||
|
||||
- `--port <port>`: WebSocket port (default comes from config/env; usually `18789`).
|
||||
- `--bind <loopback|lan|tailnet|auto|custom>`: listener bind mode.
|
||||
- `--auth <token|password>`: auth mode override.
|
||||
- `--token <token>`: token override (also sets `BOT_GATEWAY_TOKEN` for the process).
|
||||
- `--password <password>`: password override (also sets `BOT_GATEWAY_PASSWORD` for the process).
|
||||
- `--tailscale <off|serve|funnel>`: expose the Gateway via Tailscale.
|
||||
- `--tailscale-reset-on-exit`: reset Tailscale serve/funnel config on shutdown.
|
||||
- `--allow-unconfigured`: allow gateway start without `gateway.mode=local` in config.
|
||||
- `--dev`: create a dev config + workspace if missing (skips BOOTSTRAP.md).
|
||||
- `--reset`: reset dev config + credentials + sessions + workspace (requires `--dev`).
|
||||
- `--force`: kill any existing listener on the selected port before starting.
|
||||
- `--verbose`: verbose logs.
|
||||
- `--claude-cli-logs`: only show claude-cli logs in the console (and enable its stdout/stderr).
|
||||
- `--ws-log <auto|full|compact>`: websocket log style (default `auto`).
|
||||
- `--compact`: alias for `--ws-log compact`.
|
||||
- `--raw-stream`: log raw model stream events to jsonl.
|
||||
- `--raw-stream-path <path>`: raw stream jsonl path.
|
||||
|
||||
## Query a running Gateway
|
||||
|
||||
All query commands use WebSocket RPC.
|
||||
|
||||
Output modes:
|
||||
- Default: human-readable (colored in TTY).
|
||||
- `--json`: machine-readable JSON (no styling/spinner).
|
||||
- `--no-color` (or `NO_COLOR=1`): disable ANSI while keeping human layout.
|
||||
|
||||
Shared options (where supported):
|
||||
- `--url <url>`: Gateway WebSocket URL.
|
||||
- `--token <token>`: Gateway token.
|
||||
- `--password <password>`: Gateway password.
|
||||
- `--timeout <ms>`: timeout/budget (varies per command).
|
||||
- `--expect-final`: wait for a “final” response (agent calls).
|
||||
|
||||
### `gateway health`
|
||||
|
||||
```bash
|
||||
bot gateway health --url ws://127.0.0.1:18789
|
||||
```
|
||||
|
||||
### `gateway status`
|
||||
|
||||
`gateway status` shows the Gateway service (launchd/systemd/schtasks) plus an optional RPC probe.
|
||||
|
||||
```bash
|
||||
bot gateway status
|
||||
bot gateway status --json
|
||||
```
|
||||
|
||||
Options:
|
||||
- `--url <url>`: override the probe URL.
|
||||
- `--token <token>`: token auth for the probe.
|
||||
- `--password <password>`: password auth for the probe.
|
||||
- `--timeout <ms>`: probe timeout (default `10000`).
|
||||
- `--no-probe`: skip the RPC probe (service-only view).
|
||||
- `--deep`: scan system-level services too.
|
||||
|
||||
### `gateway probe`
|
||||
|
||||
`gateway probe` is the “debug everything” command. It always probes:
|
||||
- your configured remote gateway (if set), and
|
||||
- localhost (loopback) **even if remote is configured**.
|
||||
|
||||
If multiple gateways are reachable, it prints all of them. Multiple gateways are supported when you use isolated profiles/ports (e.g., a rescue bot), but most installs still run a single gateway.
|
||||
|
||||
```bash
|
||||
bot gateway probe
|
||||
bot gateway probe --json
|
||||
```
|
||||
|
||||
#### Remote over SSH (Mac app parity)
|
||||
|
||||
The macOS app “Remote over SSH” mode uses a local port-forward so the remote gateway (which may be bound to loopback only) becomes reachable at `ws://127.0.0.1:<port>`.
|
||||
|
||||
CLI equivalent:
|
||||
|
||||
```bash
|
||||
bot gateway probe --ssh user@gateway-host
|
||||
```
|
||||
|
||||
Options:
|
||||
- `--ssh <target>`: `user@host` or `user@host:port` (port defaults to `22`).
|
||||
- `--ssh-identity <path>`: identity file.
|
||||
- `--ssh-auto`: pick the first discovered gateway host as SSH target (LAN/WAB only).
|
||||
|
||||
Config (optional, used as defaults):
|
||||
- `gateway.remote.sshTarget`
|
||||
- `gateway.remote.sshIdentity`
|
||||
|
||||
### `gateway call <method>`
|
||||
|
||||
Low-level RPC helper.
|
||||
|
||||
```bash
|
||||
bot gateway call status
|
||||
bot gateway call logs.tail --params '{"sinceMs": 60000}'
|
||||
```
|
||||
|
||||
## Manage the Gateway service
|
||||
|
||||
```bash
|
||||
bot gateway install
|
||||
bot gateway start
|
||||
bot gateway stop
|
||||
bot gateway restart
|
||||
bot gateway uninstall
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `gateway install` supports `--port`, `--runtime`, `--token`, `--force`, `--json`.
|
||||
- Lifecycle commands accept `--json` for scripting.
|
||||
|
||||
## Discover gateways (Bonjour)
|
||||
|
||||
`gateway discover` scans for Gateway beacons (`_bot-gw._tcp`).
|
||||
|
||||
- Multicast DNS-SD: `local.`
|
||||
- Unicast DNS-SD (Wide-Area Bonjour): `bot.internal.` (requires split DNS + DNS server; see [/gateway/bonjour](/gateway/bonjour))
|
||||
|
||||
Only gateways with Bonjour discovery enabled (default) advertise the beacon.
|
||||
|
||||
Wide-Area discovery records include (TXT):
|
||||
- `role` (gateway role hint)
|
||||
- `transport` (transport hint, e.g. `gateway`)
|
||||
- `gatewayPort` (WebSocket port, usually `18789`)
|
||||
- `sshPort` (SSH port; defaults to `22` if not present)
|
||||
- `tailnetDns` (MagicDNS hostname, when available)
|
||||
- `gatewayTls` / `gatewayTlsSha256` (TLS enabled + cert fingerprint)
|
||||
- `cliPath` (optional hint for remote installs)
|
||||
|
||||
### `gateway discover`
|
||||
|
||||
```bash
|
||||
bot gateway discover
|
||||
```
|
||||
|
||||
Options:
|
||||
- `--timeout <ms>`: per-command timeout (browse/resolve); default `2000`.
|
||||
- `--json`: machine-readable output (also disables styling/spinner).
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
bot gateway discover --timeout 4000
|
||||
bot gateway discover --json | jq '.beacons[].wsUrl'
|
||||
```
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "`bot health`"
|
||||
description: "CLI reference for `bot health` (gateway health endpoint via RPC)"
|
||||
---
|
||||
|
||||
Fetch health from the running Gateway.
|
||||
|
||||
```bash
|
||||
bot health
|
||||
bot health --json
|
||||
bot health --verbose
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `--verbose` runs live probes and prints per-account timings when multiple accounts are configured.
|
||||
- Output includes per-agent session stores when multiple agents are configured.
|
||||
@@ -0,0 +1,286 @@
|
||||
---
|
||||
title: "`bot hooks`"
|
||||
description: "CLI reference for `bot hooks` (agent hooks)"
|
||||
---
|
||||
|
||||
Manage agent hooks (event-driven automations for commands like `/new`, `/reset`, and gateway startup).
|
||||
|
||||
Related:
|
||||
- Hooks: [Hooks](/hooks)
|
||||
- Plugin hooks: [Plugins](/plugin#plugin-hooks)
|
||||
|
||||
## List All Hooks
|
||||
|
||||
```bash
|
||||
bot hooks list
|
||||
```
|
||||
|
||||
List all discovered hooks from workspace, managed, and bundled directories.
|
||||
|
||||
**Options:**
|
||||
- `--eligible`: Show only eligible hooks (requirements met)
|
||||
- `--json`: Output as JSON
|
||||
- `-v, --verbose`: Show detailed information including missing requirements
|
||||
|
||||
**Example output:**
|
||||
|
||||
```
|
||||
Hooks (4/4 ready)
|
||||
|
||||
Ready:
|
||||
🚀 boot-md ✓ - Run BOOT.md on gateway startup
|
||||
📝 command-logger ✓ - Log all command events to a centralized audit file
|
||||
💾 session-memory ✓ - Save session context to memory when /new command is issued
|
||||
😈 soul-evil ✓ - Swap injected SOUL content during a purge window or by random chance
|
||||
```
|
||||
|
||||
**Example (verbose):**
|
||||
|
||||
```bash
|
||||
bot hooks list --verbose
|
||||
```
|
||||
|
||||
Shows missing requirements for ineligible hooks.
|
||||
|
||||
**Example (JSON):**
|
||||
|
||||
```bash
|
||||
bot hooks list --json
|
||||
```
|
||||
|
||||
Returns structured JSON for programmatic use.
|
||||
|
||||
## Get Hook Information
|
||||
|
||||
```bash
|
||||
bot hooks info <name>
|
||||
```
|
||||
|
||||
Show detailed information about a specific hook.
|
||||
|
||||
**Arguments:**
|
||||
- `<name>`: Hook name (e.g., `session-memory`)
|
||||
|
||||
**Options:**
|
||||
- `--json`: Output as JSON
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
bot hooks info session-memory
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```
|
||||
💾 session-memory ✓ Ready
|
||||
|
||||
Save session context to memory when /new command is issued
|
||||
|
||||
Details:
|
||||
Source: bot-bundled
|
||||
Path: /path/to/bot/hooks/bundled/session-memory/HOOK.md
|
||||
Handler: /path/to/bot/hooks/bundled/session-memory/handler.ts
|
||||
Homepage: https://docs.hanzo.bot/hooks#session-memory
|
||||
Events: command:new
|
||||
|
||||
Requirements:
|
||||
Config: ✓ workspace.dir
|
||||
```
|
||||
|
||||
## Check Hooks Eligibility
|
||||
|
||||
```bash
|
||||
bot hooks check
|
||||
```
|
||||
|
||||
Show summary of hook eligibility status (how many are ready vs. not ready).
|
||||
|
||||
**Options:**
|
||||
- `--json`: Output as JSON
|
||||
|
||||
**Example output:**
|
||||
|
||||
```
|
||||
Hooks Status
|
||||
|
||||
Total hooks: 4
|
||||
Ready: 4
|
||||
Not ready: 0
|
||||
```
|
||||
|
||||
## Enable a Hook
|
||||
|
||||
```bash
|
||||
bot hooks enable <name>
|
||||
```
|
||||
|
||||
Enable a specific hook by adding it to your config (`~/.bot/config.json`).
|
||||
|
||||
**Note:** Hooks managed by plugins show `plugin:<id>` in `bot hooks list` and
|
||||
can’t be enabled/disabled here. Enable/disable the plugin instead.
|
||||
|
||||
**Arguments:**
|
||||
- `<name>`: Hook name (e.g., `session-memory`)
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
bot hooks enable session-memory
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```
|
||||
✓ Enabled hook: 💾 session-memory
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- Checks if hook exists and is eligible
|
||||
- Updates `hooks.internal.entries.<name>.enabled = true` in your config
|
||||
- Saves config to disk
|
||||
|
||||
**After enabling:**
|
||||
- Restart the gateway so hooks reload (menu bar app restart on macOS, or restart your gateway process in dev).
|
||||
|
||||
## Disable a Hook
|
||||
|
||||
```bash
|
||||
bot hooks disable <name>
|
||||
```
|
||||
|
||||
Disable a specific hook by updating your config.
|
||||
|
||||
**Arguments:**
|
||||
- `<name>`: Hook name (e.g., `command-logger`)
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
bot hooks disable command-logger
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```
|
||||
⏸ Disabled hook: 📝 command-logger
|
||||
```
|
||||
|
||||
**After disabling:**
|
||||
- Restart the gateway so hooks reload
|
||||
|
||||
## Install Hooks
|
||||
|
||||
```bash
|
||||
bot hooks install <path-or-spec>
|
||||
```
|
||||
|
||||
Install a hook pack from a local folder/archive or npm.
|
||||
|
||||
**What it does:**
|
||||
- Copies the hook pack into `~/.bot/hooks/<id>`
|
||||
- Enables the installed hooks in `hooks.internal.entries.*`
|
||||
- Records the install under `hooks.internal.installs`
|
||||
|
||||
**Options:**
|
||||
- `-l, --link`: Link a local directory instead of copying (adds it to `hooks.internal.load.extraDirs`)
|
||||
|
||||
**Supported archives:** `.zip`, `.tgz`, `.tar.gz`, `.tar`
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Local directory
|
||||
bot hooks install ./my-hook-pack
|
||||
|
||||
# Local archive
|
||||
bot hooks install ./my-hook-pack.zip
|
||||
|
||||
# NPM package
|
||||
bot hooks install @bot/my-hook-pack
|
||||
|
||||
# Link a local directory without copying
|
||||
bot hooks install -l ./my-hook-pack
|
||||
```
|
||||
|
||||
## Update Hooks
|
||||
|
||||
```bash
|
||||
bot hooks update <id>
|
||||
bot hooks update --all
|
||||
```
|
||||
|
||||
Update installed hook packs (npm installs only).
|
||||
|
||||
**Options:**
|
||||
- `--all`: Update all tracked hook packs
|
||||
- `--dry-run`: Show what would change without writing
|
||||
|
||||
## Bundled Hooks
|
||||
|
||||
### session-memory
|
||||
|
||||
Saves session context to memory when you issue `/new`.
|
||||
|
||||
**Enable:**
|
||||
|
||||
```bash
|
||||
bot hooks enable session-memory
|
||||
```
|
||||
|
||||
**Output:** `~/bot/memory/YYYY-MM-DD-slug.md`
|
||||
|
||||
**See:** [session-memory documentation](/hooks#session-memory)
|
||||
|
||||
### command-logger
|
||||
|
||||
Logs all command events to a centralized audit file.
|
||||
|
||||
**Enable:**
|
||||
|
||||
```bash
|
||||
bot hooks enable command-logger
|
||||
```
|
||||
|
||||
**Output:** `~/.bot/logs/commands.log`
|
||||
|
||||
**View logs:**
|
||||
|
||||
```bash
|
||||
# Recent commands
|
||||
tail -n 20 ~/.bot/logs/commands.log
|
||||
|
||||
# Pretty-print
|
||||
cat ~/.bot/logs/commands.log | jq .
|
||||
|
||||
# Filter by action
|
||||
grep '"action":"new"' ~/.bot/logs/commands.log | jq .
|
||||
```
|
||||
|
||||
**See:** [command-logger documentation](/hooks#command-logger)
|
||||
|
||||
### soul-evil
|
||||
|
||||
Swaps injected `SOUL.md` content with `SOUL_EVIL.md` during a purge window or by random chance.
|
||||
|
||||
**Enable:**
|
||||
|
||||
```bash
|
||||
bot hooks enable soul-evil
|
||||
```
|
||||
|
||||
**See:** [SOUL Evil Hook](/hooks/soul-evil)
|
||||
|
||||
### boot-md
|
||||
|
||||
Runs `BOOT.md` when the gateway starts (after channels start).
|
||||
|
||||
**Events**: `gateway:startup`
|
||||
|
||||
**Enable**:
|
||||
|
||||
```bash
|
||||
bot hooks enable boot-md
|
||||
```
|
||||
|
||||
**See:** [boot-md documentation](/hooks#boot-md)
|
||||
@@ -0,0 +1,920 @@
|
||||
---
|
||||
title: "CLI reference"
|
||||
description: "Bot CLI reference for `bot` commands, subcommands, and options"
|
||||
---
|
||||
|
||||
This page describes the current CLI behavior. If commands change, update this doc.
|
||||
|
||||
## Command pages
|
||||
|
||||
- [`setup`](/cli/setup)
|
||||
- [`onboard`](/cli/onboard)
|
||||
- [`configure`](/cli/configure)
|
||||
- [`config`](/cli/config)
|
||||
- [`doctor`](/cli/doctor)
|
||||
- [`dashboard`](/cli/dashboard)
|
||||
- [`reset`](/cli/reset)
|
||||
- [`uninstall`](/cli/uninstall)
|
||||
- [`update`](/cli/update)
|
||||
- [`message`](/cli/message)
|
||||
- [`agent`](/cli/agent)
|
||||
- [`agents`](/cli/agents)
|
||||
- [`acp`](/cli/acp)
|
||||
- [`status`](/cli/status)
|
||||
- [`health`](/cli/health)
|
||||
- [`sessions`](/cli/sessions)
|
||||
- [`gateway`](/cli/gateway)
|
||||
- [`logs`](/cli/logs)
|
||||
- [`system`](/cli/system)
|
||||
- [`models`](/cli/models)
|
||||
- [`memory`](/cli/memory)
|
||||
- [`nodes`](/cli/nodes)
|
||||
- [`devices`](/cli/devices)
|
||||
- [`node`](/cli/node)
|
||||
- [`approvals`](/cli/approvals)
|
||||
- [`sandbox`](/cli/sandbox)
|
||||
- [`tui`](/cli/tui)
|
||||
- [`browser`](/cli/browser)
|
||||
- [`cron`](/cli/cron)
|
||||
- [`dns`](/cli/dns)
|
||||
- [`docs`](/cli/docs)
|
||||
- [`hooks`](/cli/hooks)
|
||||
- [`webhooks`](/cli/webhooks)
|
||||
- [`pairing`](/cli/pairing)
|
||||
- [`plugins`](/cli/plugins) (plugin commands)
|
||||
- [`channels`](/cli/channels)
|
||||
- [`security`](/cli/security)
|
||||
- [`skills`](/cli/skills)
|
||||
- [`voicecall`](/cli/voicecall) (plugin; if installed)
|
||||
|
||||
## Global flags
|
||||
|
||||
- `--dev`: isolate state under `~/.bot-dev` and shift default ports.
|
||||
- `--profile <name>`: isolate state under `~/.bot-<name>`.
|
||||
- `--no-color`: disable ANSI colors.
|
||||
- `--update`: shorthand for `bot update` (source installs only).
|
||||
- `-V`, `--version`, `-v`: print version and exit.
|
||||
|
||||
## Output styling
|
||||
|
||||
- ANSI colors and progress indicators only render in TTY sessions.
|
||||
- OSC-8 hyperlinks render as clickable links in supported terminals; otherwise we fall back to plain URLs.
|
||||
- `--json` (and `--plain` where supported) disables styling for clean output.
|
||||
- `--no-color` disables ANSI styling; `NO_COLOR=1` is also respected.
|
||||
- Long-running commands show a progress indicator (OSC 9;4 when supported).
|
||||
|
||||
## Color palette
|
||||
|
||||
Bot uses a lobster palette for CLI output.
|
||||
|
||||
- `accent` (#FF5A2D): headings, labels, primary highlights.
|
||||
- `accentBright` (#FF7A3D): command names, emphasis.
|
||||
- `accentDim` (#D14A22): secondary highlight text.
|
||||
- `info` (#FF8A5B): informational values.
|
||||
- `success` (#2FBF71): success states.
|
||||
- `warn` (#FFB020): warnings, fallbacks, attention.
|
||||
- `error` (#E23D2D): errors, failures.
|
||||
- `muted` (#8B7F77): de-emphasis, metadata.
|
||||
|
||||
Palette source of truth: `src/terminal/palette.ts` (aka “lobster seam”).
|
||||
|
||||
## Command tree
|
||||
|
||||
```
|
||||
bot [--dev] [--profile <name>] <command>
|
||||
setup
|
||||
onboard
|
||||
configure
|
||||
config
|
||||
get
|
||||
set
|
||||
unset
|
||||
doctor
|
||||
security
|
||||
audit
|
||||
reset
|
||||
uninstall
|
||||
update
|
||||
channels
|
||||
list
|
||||
status
|
||||
logs
|
||||
add
|
||||
remove
|
||||
login
|
||||
logout
|
||||
skills
|
||||
list
|
||||
info
|
||||
check
|
||||
plugins
|
||||
list
|
||||
info
|
||||
install
|
||||
enable
|
||||
disable
|
||||
doctor
|
||||
memory
|
||||
status
|
||||
index
|
||||
search
|
||||
message
|
||||
agent
|
||||
agents
|
||||
list
|
||||
add
|
||||
delete
|
||||
acp
|
||||
status
|
||||
health
|
||||
sessions
|
||||
gateway
|
||||
call
|
||||
health
|
||||
status
|
||||
probe
|
||||
discover
|
||||
install
|
||||
uninstall
|
||||
start
|
||||
stop
|
||||
restart
|
||||
run
|
||||
logs
|
||||
system
|
||||
event
|
||||
heartbeat last|enable|disable
|
||||
presence
|
||||
models
|
||||
list
|
||||
status
|
||||
set
|
||||
set-image
|
||||
aliases list|add|remove
|
||||
fallbacks list|add|remove|clear
|
||||
image-fallbacks list|add|remove|clear
|
||||
scan
|
||||
auth add|setup-token|paste-token
|
||||
auth order get|set|clear
|
||||
sandbox
|
||||
list
|
||||
recreate
|
||||
explain
|
||||
cron
|
||||
status
|
||||
list
|
||||
add
|
||||
edit
|
||||
rm
|
||||
enable
|
||||
disable
|
||||
runs
|
||||
run
|
||||
nodes
|
||||
devices
|
||||
node
|
||||
run
|
||||
status
|
||||
install
|
||||
uninstall
|
||||
start
|
||||
stop
|
||||
restart
|
||||
approvals
|
||||
get
|
||||
set
|
||||
allowlist add|remove
|
||||
browser
|
||||
status
|
||||
start
|
||||
stop
|
||||
reset-profile
|
||||
tabs
|
||||
open
|
||||
focus
|
||||
close
|
||||
profiles
|
||||
create-profile
|
||||
delete-profile
|
||||
screenshot
|
||||
snapshot
|
||||
navigate
|
||||
resize
|
||||
click
|
||||
type
|
||||
press
|
||||
hover
|
||||
drag
|
||||
select
|
||||
upload
|
||||
fill
|
||||
dialog
|
||||
wait
|
||||
evaluate
|
||||
console
|
||||
pdf
|
||||
hooks
|
||||
list
|
||||
info
|
||||
check
|
||||
enable
|
||||
disable
|
||||
install
|
||||
update
|
||||
webhooks
|
||||
gmail setup|run
|
||||
pairing
|
||||
list
|
||||
approve
|
||||
docs
|
||||
dns
|
||||
setup
|
||||
tui
|
||||
```
|
||||
|
||||
Note: plugins can add additional top-level commands (for example `bot voicecall`).
|
||||
|
||||
## Security
|
||||
|
||||
- `bot security audit` — audit config + local state for common security foot-guns.
|
||||
- `bot security audit --deep` — best-effort live Gateway probe.
|
||||
- `bot security audit --fix` — tighten safe defaults and chmod state/config.
|
||||
|
||||
## Plugins
|
||||
|
||||
Manage extensions and their config:
|
||||
|
||||
- `bot plugins list` — discover plugins (use `--json` for machine output).
|
||||
- `bot plugins info <id>` — show details for a plugin.
|
||||
- `bot plugins install <path|.tgz|npm-spec>` — install a plugin (or add a plugin path to `plugins.load.paths`).
|
||||
- `bot plugins enable <id>` / `disable <id>` — toggle `plugins.entries.<id>.enabled`.
|
||||
- `bot plugins doctor` — report plugin load errors.
|
||||
|
||||
Most plugin changes require a gateway restart. See [/plugin](/plugin).
|
||||
|
||||
## Memory
|
||||
|
||||
Vector search over `MEMORY.md` + `memory/*.md`:
|
||||
|
||||
- `bot memory status` — show index stats.
|
||||
- `bot memory index` — reindex memory files.
|
||||
- `bot memory search "<query>"` — semantic search over memory.
|
||||
|
||||
## Chat slash commands
|
||||
|
||||
Chat messages support `/...` commands (text and native). See [/tools/slash-commands](/tools/slash-commands).
|
||||
|
||||
Highlights:
|
||||
- `/status` for quick diagnostics.
|
||||
- `/config` for persisted config changes.
|
||||
- `/debug` for runtime-only config overrides (memory, not disk; requires `commands.debug: true`).
|
||||
|
||||
## Setup + onboarding
|
||||
|
||||
### `setup`
|
||||
Initialize config + workspace.
|
||||
|
||||
Options:
|
||||
- `--workspace <dir>`: agent workspace path (default `~/bot`).
|
||||
- `--wizard`: run the onboarding wizard.
|
||||
- `--non-interactive`: run wizard without prompts.
|
||||
- `--mode <local|remote>`: wizard mode.
|
||||
- `--remote-url <url>`: remote Gateway URL.
|
||||
- `--remote-token <token>`: remote Gateway token.
|
||||
|
||||
Wizard auto-runs when any wizard flags are present (`--non-interactive`, `--mode`, `--remote-url`, `--remote-token`).
|
||||
|
||||
### `onboard`
|
||||
Interactive wizard to set up gateway, workspace, and skills.
|
||||
|
||||
Options:
|
||||
- `--workspace <dir>`
|
||||
- `--reset` (reset config + credentials + sessions + workspace before wizard)
|
||||
- `--non-interactive`
|
||||
- `--mode <local|remote>`
|
||||
- `--flow <quickstart|advanced|manual>` (manual is an alias for advanced)
|
||||
- `--auth-choice <setup-token|claude-cli|token|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|codex-cli|gemini-api-key|zai-api-key|apiKey|minimax-api|opencode-zen|skip>`
|
||||
- `--token-provider <id>` (non-interactive; used with `--auth-choice token`)
|
||||
- `--token <token>` (non-interactive; used with `--auth-choice token`)
|
||||
- `--token-profile-id <id>` (non-interactive; default: `<provider>:manual`)
|
||||
- `--token-expires-in <duration>` (non-interactive; e.g. `365d`, `12h`)
|
||||
- `--anthropic-api-key <key>`
|
||||
- `--openai-api-key <key>`
|
||||
- `--openrouter-api-key <key>`
|
||||
- `--ai-gateway-api-key <key>`
|
||||
- `--moonshot-api-key <key>`
|
||||
- `--kimi-code-api-key <key>`
|
||||
- `--gemini-api-key <key>`
|
||||
- `--zai-api-key <key>`
|
||||
- `--minimax-api-key <key>`
|
||||
- `--opencode-zen-api-key <key>`
|
||||
- `--gateway-port <port>`
|
||||
- `--gateway-bind <loopback|lan|tailnet|auto|custom>`
|
||||
- `--gateway-auth <off|token|password>`
|
||||
- `--gateway-token <token>`
|
||||
- `--gateway-password <password>`
|
||||
- `--remote-url <url>`
|
||||
- `--remote-token <token>`
|
||||
- `--tailscale <off|serve|funnel>`
|
||||
- `--tailscale-reset-on-exit`
|
||||
- `--install-daemon`
|
||||
- `--no-install-daemon` (alias: `--skip-daemon`)
|
||||
- `--daemon-runtime <node|bun>`
|
||||
- `--skip-channels`
|
||||
- `--skip-skills`
|
||||
- `--skip-health`
|
||||
- `--skip-ui`
|
||||
- `--node-manager <npm|pnpm|bun>` (pnpm recommended; bun not recommended for Gateway runtime)
|
||||
- `--json`
|
||||
|
||||
### `configure`
|
||||
Interactive configuration wizard (models, channels, skills, gateway).
|
||||
|
||||
### `config`
|
||||
Non-interactive config helpers (get/set/unset). Running `bot config` with no
|
||||
subcommand launches the wizard.
|
||||
|
||||
Subcommands:
|
||||
- `config get <path>`: print a config value (dot/bracket path).
|
||||
- `config set <path> <value>`: set a value (JSON5 or raw string).
|
||||
- `config unset <path>`: remove a value.
|
||||
|
||||
### `doctor`
|
||||
Health checks + quick fixes (config + gateway + legacy services).
|
||||
|
||||
Options:
|
||||
- `--no-workspace-suggestions`: disable workspace memory hints.
|
||||
- `--yes`: accept defaults without prompting (headless).
|
||||
- `--non-interactive`: skip prompts; apply safe migrations only.
|
||||
- `--deep`: scan system services for extra gateway installs.
|
||||
|
||||
## Channel helpers
|
||||
|
||||
### `channels`
|
||||
Manage chat channel accounts (WhatsApp/Telegram/Discord/Google Chat/Slack/Mattermost (plugin)/Signal/iMessage/MS Teams).
|
||||
|
||||
Subcommands:
|
||||
- `channels list`: show configured channels and auth profiles (Claude Code + Codex CLI OAuth sync included).
|
||||
- `channels status`: check gateway reachability and channel health (`--probe` runs extra checks; use `bot health` or `bot status --deep` for gateway health probes).
|
||||
- Tip: `channels status` prints warnings with suggested fixes when it can detect common misconfigurations (then points you to `bot doctor`).
|
||||
- `channels logs`: show recent channel logs from the gateway log file.
|
||||
- `channels add`: wizard-style setup when no flags are passed; flags switch to non-interactive mode.
|
||||
- `channels remove`: disable by default; pass `--delete` to remove config entries without prompts.
|
||||
- `channels login`: interactive channel login (WhatsApp Web only).
|
||||
- `channels logout`: log out of a channel session (if supported).
|
||||
|
||||
Common options:
|
||||
- `--channel <name>`: `whatsapp|telegram|discord|googlechat|slack|mattermost|signal|imessage|msteams`
|
||||
- `--account <id>`: channel account id (default `default`)
|
||||
- `--name <label>`: display name for the account
|
||||
|
||||
`channels login` options:
|
||||
- `--channel <channel>` (default `whatsapp`; supports `whatsapp`/`web`)
|
||||
- `--account <id>`
|
||||
- `--verbose`
|
||||
|
||||
`channels logout` options:
|
||||
- `--channel <channel>` (default `whatsapp`)
|
||||
- `--account <id>`
|
||||
|
||||
`channels list` options:
|
||||
- `--no-usage`: skip model provider usage/quota snapshots (OAuth/API-backed only).
|
||||
- `--json`: output JSON (includes usage unless `--no-usage` is set).
|
||||
|
||||
`channels logs` options:
|
||||
- `--channel <name|all>` (default `all`)
|
||||
- `--lines <n>` (default `200`)
|
||||
- `--json`
|
||||
|
||||
OAuth sync sources:
|
||||
- Claude Code → `anthropic:claude-cli`
|
||||
- macOS: Keychain item "Claude Code-credentials" (choose "Always Allow" to avoid launchd prompts)
|
||||
- Linux/Windows: `~/.claude/.credentials.json`
|
||||
- `~/.codex/auth.json` → `openai-codex:codex-cli`
|
||||
|
||||
More detail: [/concepts/oauth](/concepts/oauth)
|
||||
|
||||
Examples:
|
||||
```bash
|
||||
bot channels add --channel telegram --account alerts --name "Alerts Bot" --token $TELEGRAM_BOT_TOKEN
|
||||
bot channels add --channel discord --account work --name "Work Bot" --token $DISCORD_BOT_TOKEN
|
||||
bot channels remove --channel discord --account work --delete
|
||||
bot channels status --probe
|
||||
bot status --deep
|
||||
```
|
||||
|
||||
### `skills`
|
||||
List and inspect available skills plus readiness info.
|
||||
|
||||
Subcommands:
|
||||
- `skills list`: list skills (default when no subcommand).
|
||||
- `skills info <name>`: show details for one skill.
|
||||
- `skills check`: summary of ready vs missing requirements.
|
||||
|
||||
Options:
|
||||
- `--eligible`: show only ready skills.
|
||||
- `--json`: output JSON (no styling).
|
||||
- `-v`, `--verbose`: include missing requirements detail.
|
||||
|
||||
Tip: use `npx skills` to search, install, and sync skills.
|
||||
|
||||
### `pairing`
|
||||
Approve DM pairing requests across channels.
|
||||
|
||||
Subcommands:
|
||||
- `pairing list <channel> [--json]`
|
||||
- `pairing approve <channel> <code> [--notify]`
|
||||
|
||||
### `webhooks gmail`
|
||||
Gmail Pub/Sub hook setup + runner. See [/automation/gmail-pubsub](/automation/gmail-pubsub).
|
||||
|
||||
Subcommands:
|
||||
- `webhooks gmail setup` (requires `--account <email>`; supports `--project`, `--topic`, `--subscription`, `--label`, `--hook-url`, `--hook-token`, `--push-token`, `--bind`, `--port`, `--path`, `--include-body`, `--max-bytes`, `--renew-minutes`, `--tailscale`, `--tailscale-path`, `--tailscale-target`, `--push-endpoint`, `--json`)
|
||||
- `webhooks gmail run` (runtime overrides for the same flags)
|
||||
|
||||
### `dns setup`
|
||||
Wide-area discovery DNS helper (CoreDNS + Tailscale). See [/gateway/discovery](/gateway/discovery).
|
||||
|
||||
Options:
|
||||
- `--apply`: install/update CoreDNS config (requires sudo; macOS only).
|
||||
|
||||
## Messaging + agent
|
||||
|
||||
### `message`
|
||||
Unified outbound messaging + channel actions.
|
||||
|
||||
See: [/cli/message](/cli/message)
|
||||
|
||||
Subcommands:
|
||||
- `message send|poll|react|reactions|read|edit|delete|pin|unpin|pins|permissions|search|timeout|kick|ban`
|
||||
- `message thread <create|list|reply>`
|
||||
- `message emoji <list|upload>`
|
||||
- `message sticker <send|upload>`
|
||||
- `message role <info|add|remove>`
|
||||
- `message channel <info|list>`
|
||||
- `message member info`
|
||||
- `message voice status`
|
||||
- `message event <list|create>`
|
||||
|
||||
Examples:
|
||||
- `bot message send --target +15555550123 --message "Hi"`
|
||||
- `bot message poll --channel discord --target channel:123 --poll-question "Snack?" --poll-option Pizza --poll-option Sushi`
|
||||
|
||||
### `agent`
|
||||
Run one agent turn via the Gateway (or `--local` embedded).
|
||||
|
||||
Required:
|
||||
- `--message <text>`
|
||||
|
||||
Options:
|
||||
- `--to <dest>` (for session key and optional delivery)
|
||||
- `--session-id <id>`
|
||||
- `--thinking <off|minimal|low|medium|high|xhigh>` (GPT-5.2 + Codex models only)
|
||||
- `--verbose <on|full|off>`
|
||||
- `--channel <whatsapp|telegram|discord|slack|mattermost|signal|imessage|msteams>`
|
||||
- `--local`
|
||||
- `--deliver`
|
||||
- `--json`
|
||||
- `--timeout <seconds>`
|
||||
|
||||
### `agents`
|
||||
Manage isolated agents (workspaces + auth + routing).
|
||||
|
||||
#### `agents list`
|
||||
List configured agents.
|
||||
|
||||
Options:
|
||||
- `--json`
|
||||
- `--bindings`
|
||||
|
||||
#### `agents add [name]`
|
||||
Add a new isolated agent. Runs the guided wizard unless flags (or `--non-interactive`) are passed; `--workspace` is required in non-interactive mode.
|
||||
|
||||
Options:
|
||||
- `--workspace <dir>`
|
||||
- `--model <id>`
|
||||
- `--agent-dir <dir>`
|
||||
- `--bind <channel[:accountId]>` (repeatable)
|
||||
- `--non-interactive`
|
||||
- `--json`
|
||||
|
||||
Binding specs use `channel[:accountId]`. When `accountId` is omitted for WhatsApp, the default account id is used.
|
||||
|
||||
#### `agents delete <id>`
|
||||
Delete an agent and prune its workspace + state.
|
||||
|
||||
Options:
|
||||
- `--force`
|
||||
- `--json`
|
||||
|
||||
### `acp`
|
||||
Run the ACP bridge that connects IDEs to the Gateway.
|
||||
|
||||
See [`acp`](/cli/acp) for full options and examples.
|
||||
|
||||
### `status`
|
||||
Show linked session health and recent recipients.
|
||||
|
||||
Options:
|
||||
- `--json`
|
||||
- `--all` (full diagnosis; read-only, pasteable)
|
||||
- `--deep` (probe channels)
|
||||
- `--usage` (show model provider usage/quota)
|
||||
- `--timeout <ms>`
|
||||
- `--verbose`
|
||||
- `--debug` (alias for `--verbose`)
|
||||
|
||||
Notes:
|
||||
- Overview includes Gateway + node host service status when available.
|
||||
|
||||
### Usage tracking
|
||||
Bot can surface provider usage/quota when OAuth/API creds are available.
|
||||
|
||||
Surfaces:
|
||||
- `/status` (adds a short provider usage line when available)
|
||||
- `bot status --usage` (prints full provider breakdown)
|
||||
- macOS menu bar (Usage section under Context)
|
||||
|
||||
Notes:
|
||||
- Data comes directly from provider usage endpoints (no estimates).
|
||||
- Providers: Anthropic, GitHub Copilot, OpenAI Codex OAuth, plus Gemini CLI/Antigravity when those provider plugins are enabled.
|
||||
- If no matching credentials exist, usage is hidden.
|
||||
- Details: see [Usage tracking](/concepts/usage-tracking).
|
||||
|
||||
### `health`
|
||||
Fetch health from the running Gateway.
|
||||
|
||||
Options:
|
||||
- `--json`
|
||||
- `--timeout <ms>`
|
||||
- `--verbose`
|
||||
|
||||
### `sessions`
|
||||
List stored conversation sessions.
|
||||
|
||||
Options:
|
||||
- `--json`
|
||||
- `--verbose`
|
||||
- `--store <path>`
|
||||
- `--active <minutes>`
|
||||
|
||||
## Reset / Uninstall
|
||||
|
||||
### `reset`
|
||||
Reset local config/state (keeps the CLI installed).
|
||||
|
||||
Options:
|
||||
- `--scope <config|config+creds+sessions|full>`
|
||||
- `--yes`
|
||||
- `--non-interactive`
|
||||
- `--dry-run`
|
||||
|
||||
Notes:
|
||||
- `--non-interactive` requires `--scope` and `--yes`.
|
||||
|
||||
### `uninstall`
|
||||
Uninstall the gateway service + local data (CLI remains).
|
||||
|
||||
Options:
|
||||
- `--service`
|
||||
- `--state`
|
||||
- `--workspace`
|
||||
- `--app`
|
||||
- `--all`
|
||||
- `--yes`
|
||||
- `--non-interactive`
|
||||
- `--dry-run`
|
||||
|
||||
Notes:
|
||||
- `--non-interactive` requires `--yes` and explicit scopes (or `--all`).
|
||||
|
||||
## Gateway
|
||||
|
||||
### `gateway`
|
||||
Run the WebSocket Gateway.
|
||||
|
||||
Options:
|
||||
- `--port <port>`
|
||||
- `--bind <loopback|tailnet|lan|auto|custom>`
|
||||
- `--token <token>`
|
||||
- `--auth <token|password>`
|
||||
- `--password <password>`
|
||||
- `--tailscale <off|serve|funnel>`
|
||||
- `--tailscale-reset-on-exit`
|
||||
- `--allow-unconfigured`
|
||||
- `--dev`
|
||||
- `--reset` (reset dev config + credentials + sessions + workspace)
|
||||
- `--force` (kill existing listener on port)
|
||||
- `--verbose`
|
||||
- `--claude-cli-logs`
|
||||
- `--ws-log <auto|full|compact>`
|
||||
- `--compact` (alias for `--ws-log compact`)
|
||||
- `--raw-stream`
|
||||
- `--raw-stream-path <path>`
|
||||
|
||||
### `gateway service`
|
||||
Manage the Gateway service (launchd/systemd/schtasks).
|
||||
|
||||
Subcommands:
|
||||
- `gateway status` (probes the Gateway RPC by default)
|
||||
- `gateway install` (service install)
|
||||
- `gateway uninstall`
|
||||
- `gateway start`
|
||||
- `gateway stop`
|
||||
- `gateway restart`
|
||||
|
||||
Notes:
|
||||
- `gateway status` probes the Gateway RPC by default using the service’s resolved port/config (override with `--url/--token/--password`).
|
||||
- `gateway status` supports `--no-probe`, `--deep`, and `--json` for scripting.
|
||||
- `gateway status` also surfaces legacy or extra gateway services when it can detect them (`--deep` adds system-level scans). Profile-named Bot services are treated as first-class and aren't flagged as "extra".
|
||||
- `gateway status` prints which config path the CLI uses vs which config the service likely uses (service env), plus the resolved probe target URL.
|
||||
- `gateway install|uninstall|start|stop|restart` support `--json` for scripting (default output stays human-friendly).
|
||||
- `gateway install` defaults to Node runtime; bun is **not recommended** (WhatsApp/Telegram bugs).
|
||||
- `gateway install` options: `--port`, `--runtime`, `--token`, `--force`, `--json`.
|
||||
|
||||
### `logs`
|
||||
Tail Gateway file logs via RPC.
|
||||
|
||||
Notes:
|
||||
- TTY sessions render a colorized, structured view; non-TTY falls back to plain text.
|
||||
- `--json` emits line-delimited JSON (one log event per line).
|
||||
|
||||
Examples:
|
||||
```bash
|
||||
bot logs --follow
|
||||
bot logs --limit 200
|
||||
bot logs --plain
|
||||
bot logs --json
|
||||
bot logs --no-color
|
||||
```
|
||||
|
||||
### `gateway <subcommand>`
|
||||
Gateway CLI helpers (use `--url`, `--token`, `--password`, `--timeout`, `--expect-final` for RPC subcommands).
|
||||
|
||||
Subcommands:
|
||||
- `gateway call <method> [--params <json>]`
|
||||
- `gateway health`
|
||||
- `gateway status`
|
||||
- `gateway probe`
|
||||
- `gateway discover`
|
||||
- `gateway install|uninstall|start|stop|restart`
|
||||
- `gateway run`
|
||||
|
||||
Common RPCs:
|
||||
- `config.apply` (validate + write config + restart + wake)
|
||||
- `config.patch` (merge a partial update + restart + wake)
|
||||
- `update.run` (run update + restart + wake)
|
||||
|
||||
Tip: when calling `config.set`/`config.apply`/`config.patch` directly, pass `baseHash` from
|
||||
`config.get` if a config already exists.
|
||||
|
||||
## Models
|
||||
|
||||
See [/concepts/models](/concepts/models) for fallback behavior and scanning strategy.
|
||||
|
||||
Preferred Anthropic auth (CLI token, not API key):
|
||||
|
||||
```bash
|
||||
claude setup-token
|
||||
bot models status
|
||||
```
|
||||
|
||||
### `models` (root)
|
||||
`bot models` is an alias for `models status`.
|
||||
|
||||
Root options:
|
||||
- `--status-json` (alias for `models status --json`)
|
||||
- `--status-plain` (alias for `models status --plain`)
|
||||
|
||||
### `models list`
|
||||
Options:
|
||||
- `--all`
|
||||
- `--local`
|
||||
- `--provider <name>`
|
||||
- `--json`
|
||||
- `--plain`
|
||||
|
||||
### `models status`
|
||||
Options:
|
||||
- `--json`
|
||||
- `--plain`
|
||||
- `--check` (exit 1=expired/missing, 2=expiring)
|
||||
- `--probe` (live probe of configured auth profiles)
|
||||
- `--probe-provider <name>`
|
||||
- `--probe-profile <id>` (repeat or comma-separated)
|
||||
- `--probe-timeout <ms>`
|
||||
- `--probe-concurrency <n>`
|
||||
- `--probe-max-tokens <n>`
|
||||
|
||||
Always includes the auth overview and OAuth expiry status for profiles in the auth store.
|
||||
`--probe` runs live requests (may consume tokens and trigger rate limits).
|
||||
|
||||
### `models set <model>`
|
||||
Set `agents.defaults.model.primary`.
|
||||
|
||||
### `models set-image <model>`
|
||||
Set `agents.defaults.imageModel.primary`.
|
||||
|
||||
### `models aliases list|add|remove`
|
||||
Options:
|
||||
- `list`: `--json`, `--plain`
|
||||
- `add <alias> <model>`
|
||||
- `remove <alias>`
|
||||
|
||||
### `models fallbacks list|add|remove|clear`
|
||||
Options:
|
||||
- `list`: `--json`, `--plain`
|
||||
- `add <model>`
|
||||
- `remove <model>`
|
||||
- `clear`
|
||||
|
||||
### `models image-fallbacks list|add|remove|clear`
|
||||
Options:
|
||||
- `list`: `--json`, `--plain`
|
||||
- `add <model>`
|
||||
- `remove <model>`
|
||||
- `clear`
|
||||
|
||||
### `models scan`
|
||||
Options:
|
||||
- `--min-params <b>`
|
||||
- `--max-age-days <days>`
|
||||
- `--provider <name>`
|
||||
- `--max-candidates <n>`
|
||||
- `--timeout <ms>`
|
||||
- `--concurrency <n>`
|
||||
- `--no-probe`
|
||||
- `--yes`
|
||||
- `--no-input`
|
||||
- `--set-default`
|
||||
- `--set-image`
|
||||
- `--json`
|
||||
|
||||
### `models auth add|setup-token|paste-token`
|
||||
Options:
|
||||
- `add`: interactive auth helper
|
||||
- `setup-token`: `--provider <name>` (default `anthropic`), `--yes`
|
||||
- `paste-token`: `--provider <name>`, `--profile-id <id>`, `--expires-in <duration>`
|
||||
|
||||
### `models auth order get|set|clear`
|
||||
Options:
|
||||
- `get`: `--provider <name>`, `--agent <id>`, `--json`
|
||||
- `set`: `--provider <name>`, `--agent <id>`, `<profileIds...>`
|
||||
- `clear`: `--provider <name>`, `--agent <id>`
|
||||
|
||||
## System
|
||||
|
||||
### `system event`
|
||||
Enqueue a system event and optionally trigger a heartbeat (Gateway RPC).
|
||||
|
||||
Required:
|
||||
- `--text <text>`
|
||||
|
||||
Options:
|
||||
- `--mode <now|next-heartbeat>`
|
||||
- `--json`
|
||||
- `--url`, `--token`, `--timeout`, `--expect-final`
|
||||
|
||||
### `system heartbeat last|enable|disable`
|
||||
Heartbeat controls (Gateway RPC).
|
||||
|
||||
Options:
|
||||
- `--json`
|
||||
- `--url`, `--token`, `--timeout`, `--expect-final`
|
||||
|
||||
### `system presence`
|
||||
List system presence entries (Gateway RPC).
|
||||
|
||||
Options:
|
||||
- `--json`
|
||||
- `--url`, `--token`, `--timeout`, `--expect-final`
|
||||
|
||||
## Cron
|
||||
Manage scheduled jobs (Gateway RPC). See [/automation/cron-jobs](/automation/cron-jobs).
|
||||
|
||||
Subcommands:
|
||||
- `cron status [--json]`
|
||||
- `cron list [--all] [--json]` (table output by default; use `--json` for raw)
|
||||
- `cron add` (alias: `create`; requires `--name` and exactly one of `--at` | `--every` | `--cron`, and exactly one payload of `--system-event` | `--message`)
|
||||
- `cron edit <id>` (patch fields)
|
||||
- `cron rm <id>` (aliases: `remove`, `delete`)
|
||||
- `cron enable <id>`
|
||||
- `cron disable <id>`
|
||||
- `cron runs --id <id> [--limit <n>]`
|
||||
- `cron run <id> [--force]`
|
||||
|
||||
All `cron` commands accept `--url`, `--token`, `--timeout`, `--expect-final`.
|
||||
|
||||
## Node host
|
||||
|
||||
`node` runs a **headless node host** or manages it as a background service. See
|
||||
[`bot node`](/cli/node).
|
||||
|
||||
Subcommands:
|
||||
- `node run --host <gateway-host> --port 18789`
|
||||
- `node status`
|
||||
- `node install [--host <gateway-host>] [--port <port>] [--tls] [--tls-fingerprint <sha256>] [--node-id <id>] [--display-name <name>] [--runtime <node|bun>] [--force]`
|
||||
- `node uninstall`
|
||||
- `node stop`
|
||||
- `node restart`
|
||||
|
||||
## Nodes
|
||||
|
||||
`nodes` talks to the Gateway and targets paired nodes. See [/nodes](/nodes).
|
||||
|
||||
Common options:
|
||||
- `--url`, `--token`, `--timeout`, `--json`
|
||||
|
||||
Subcommands:
|
||||
- `nodes status [--connected] [--last-connected <duration>]`
|
||||
- `nodes describe --node <id|name|ip>`
|
||||
- `nodes list [--connected] [--last-connected <duration>]`
|
||||
- `nodes pending`
|
||||
- `nodes approve <requestId>`
|
||||
- `nodes reject <requestId>`
|
||||
- `nodes rename --node <id|name|ip> --name <displayName>`
|
||||
- `nodes invoke --node <id|name|ip> --command <command> [--params <json>] [--invoke-timeout <ms>] [--idempotency-key <key>]`
|
||||
- `nodes run --node <id|name|ip> [--cwd <path>] [--env KEY=VAL] [--command-timeout <ms>] [--needs-screen-recording] [--invoke-timeout <ms>] <command...>` (mac node or headless node host)
|
||||
- `nodes notify --node <id|name|ip> [--title <text>] [--body <text>] [--sound <name>] [--priority <passive|active|timeSensitive>] [--delivery <system|overlay|auto>] [--invoke-timeout <ms>]` (mac only)
|
||||
|
||||
Camera:
|
||||
- `nodes camera list --node <id|name|ip>`
|
||||
- `nodes camera snap --node <id|name|ip> [--facing front|back|both] [--device-id <id>] [--max-width <px>] [--quality <0-1>] [--delay-ms <ms>] [--invoke-timeout <ms>]`
|
||||
- `nodes camera clip --node <id|name|ip> [--facing front|back] [--device-id <id>] [--duration <ms|10s|1m>] [--no-audio] [--invoke-timeout <ms>]`
|
||||
|
||||
Canvas + screen:
|
||||
- `nodes canvas snapshot --node <id|name|ip> [--format png|jpg|jpeg] [--max-width <px>] [--quality <0-1>] [--invoke-timeout <ms>]`
|
||||
- `nodes canvas present --node <id|name|ip> [--target <urlOrPath>] [--x <px>] [--y <px>] [--width <px>] [--height <px>] [--invoke-timeout <ms>]`
|
||||
- `nodes canvas hide --node <id|name|ip> [--invoke-timeout <ms>]`
|
||||
- `nodes canvas navigate <url> --node <id|name|ip> [--invoke-timeout <ms>]`
|
||||
- `nodes canvas eval [<js>] --node <id|name|ip> [--js <code>] [--invoke-timeout <ms>]`
|
||||
- `nodes canvas a2ui push --node <id|name|ip> (--jsonl <path> | --text <text>) [--invoke-timeout <ms>]`
|
||||
- `nodes canvas a2ui reset --node <id|name|ip> [--invoke-timeout <ms>]`
|
||||
- `nodes screen record --node <id|name|ip> [--screen <index>] [--duration <ms|10s>] [--fps <n>] [--no-audio] [--out <path>] [--invoke-timeout <ms>]`
|
||||
|
||||
Location:
|
||||
- `nodes location get --node <id|name|ip> [--max-age <ms>] [--accuracy <coarse|balanced|precise>] [--location-timeout <ms>] [--invoke-timeout <ms>]`
|
||||
|
||||
## Browser
|
||||
|
||||
Browser control CLI (dedicated Chrome/Brave/Edge/Chromium). See [`bot browser`](/cli/browser) and the [Browser tool](/tools/browser).
|
||||
|
||||
Common options:
|
||||
- `--url <controlUrl>`
|
||||
- `--browser-profile <name>`
|
||||
- `--json`
|
||||
|
||||
Manage:
|
||||
- `browser status`
|
||||
- `browser start`
|
||||
- `browser stop`
|
||||
- `browser reset-profile`
|
||||
- `browser tabs`
|
||||
- `browser open <url>`
|
||||
- `browser focus <targetId>`
|
||||
- `browser close [targetId]`
|
||||
- `browser profiles`
|
||||
- `browser create-profile --name <name> [--color <hex>] [--cdp-url <url>]`
|
||||
- `browser delete-profile --name <name>`
|
||||
|
||||
Inspect:
|
||||
- `browser screenshot [targetId] [--full-page] [--ref <ref>] [--element <selector>] [--type png|jpeg]`
|
||||
- `browser snapshot [--format aria|ai] [--target-id <id>] [--limit <n>] [--interactive] [--compact] [--depth <n>] [--selector <sel>] [--out <path>]`
|
||||
|
||||
Actions:
|
||||
- `browser navigate <url> [--target-id <id>]`
|
||||
- `browser resize <width> <height> [--target-id <id>]`
|
||||
- `browser click <ref> [--double] [--button <left|right|middle>] [--modifiers <csv>] [--target-id <id>]`
|
||||
- `browser type <ref> <text> [--submit] [--slowly] [--target-id <id>]`
|
||||
- `browser press <key> [--target-id <id>]`
|
||||
- `browser hover <ref> [--target-id <id>]`
|
||||
- `browser drag <startRef> <endRef> [--target-id <id>]`
|
||||
- `browser select <ref> <values...> [--target-id <id>]`
|
||||
- `browser upload <paths...> [--ref <ref>] [--input-ref <ref>] [--element <selector>] [--target-id <id>] [--timeout-ms <ms>]`
|
||||
- `browser fill [--fields <json>] [--fields-file <path>] [--target-id <id>]`
|
||||
- `browser dialog --accept|--dismiss [--prompt <text>] [--target-id <id>] [--timeout-ms <ms>]`
|
||||
- `browser wait [--time <ms>] [--text <value>] [--text-gone <value>] [--target-id <id>]`
|
||||
- `browser evaluate --fn <code> [--ref <ref>] [--target-id <id>]`
|
||||
- `browser console [--level <error|warn|info>] [--target-id <id>]`
|
||||
- `browser pdf [--target-id <id>]`
|
||||
|
||||
## Docs search
|
||||
|
||||
### `docs [query...]`
|
||||
Search the live docs index.
|
||||
|
||||
## TUI
|
||||
|
||||
### `tui`
|
||||
Open the terminal UI connected to the Gateway.
|
||||
|
||||
Options:
|
||||
- `--url <url>`
|
||||
- `--token <token>`
|
||||
- `--password <password>`
|
||||
- `--session <key>`
|
||||
- `--deliver`
|
||||
- `--thinking <level>`
|
||||
- `--message <text>`
|
||||
- `--timeout-ms <ms>` (defaults to `agents.defaults.timeoutSeconds`)
|
||||
- `--history-limit <n>`
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: "`bot logs`"
|
||||
description: "CLI reference for `bot logs` (tail gateway logs via RPC)"
|
||||
---
|
||||
|
||||
Tail Gateway file logs over RPC (works in remote mode).
|
||||
|
||||
Related:
|
||||
- Logging overview: [Logging](/logging)
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
bot logs
|
||||
bot logs --follow
|
||||
bot logs --json
|
||||
bot logs --limit 500
|
||||
```
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: "`bot memory`"
|
||||
description: "CLI reference for `bot memory` (status/index/search)"
|
||||
---
|
||||
|
||||
Manage semantic memory indexing and search.
|
||||
Provided by the active memory plugin (default: `memory-core`; set `plugins.slots.memory = "none"` to disable).
|
||||
|
||||
Related:
|
||||
- Memory concept: [Memory](/concepts/memory)
|
||||
- Plugins: [Plugins](/plugins)
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
bot memory status
|
||||
bot memory status --deep
|
||||
bot memory status --deep --index
|
||||
bot memory status --deep --index --verbose
|
||||
bot memory index
|
||||
bot memory index --verbose
|
||||
bot memory search "release checklist"
|
||||
bot memory status --agent main
|
||||
bot memory index --agent main --verbose
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
Common:
|
||||
|
||||
- `--agent <id>`: scope to a single agent (default: all configured agents).
|
||||
- `--verbose`: emit detailed logs during probes and indexing.
|
||||
|
||||
Notes:
|
||||
- `memory status --deep` probes vector + embedding availability.
|
||||
- `memory status --deep --index` runs a reindex if the store is dirty.
|
||||
- `memory index --verbose` prints per-phase details (provider, model, sources, batch activity).
|
||||
@@ -0,0 +1,224 @@
|
||||
---
|
||||
title: "`bot message`"
|
||||
description: "CLI reference for `bot message` (send + channel actions)"
|
||||
---
|
||||
|
||||
Single outbound command for sending messages and channel actions
|
||||
(Discord/Google Chat/Slack/Mattermost (plugin)/Telegram/WhatsApp/Signal/iMessage/MS Teams).
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
bot message <subcommand> [flags]
|
||||
```
|
||||
|
||||
Channel selection:
|
||||
- `--channel` required if more than one channel is configured.
|
||||
- If exactly one channel is configured, it becomes the default.
|
||||
- Values: `whatsapp|telegram|discord|googlechat|slack|mattermost|signal|imessage|msteams` (Mattermost requires plugin)
|
||||
|
||||
Target formats (`--target`):
|
||||
- WhatsApp: E.164 or group JID
|
||||
- Telegram: chat id or `@username`
|
||||
- Discord: `channel:<id>` or `user:<id>` (or `<@id>` mention; raw numeric ids are treated as channels)
|
||||
- Google Chat: `spaces/<spaceId>` or `users/<userId>`
|
||||
- Slack: `channel:<id>` or `user:<id>` (raw channel id is accepted)
|
||||
- Mattermost (plugin): `channel:<id>`, `user:<id>`, or `@username` (bare ids are treated as channels)
|
||||
- Signal: `+E.164`, `group:<id>`, `signal:+E.164`, `signal:group:<id>`, or `username:<name>`/`u:<name>`
|
||||
- iMessage: handle, `chat_id:<id>`, `chat_guid:<guid>`, or `chat_identifier:<id>`
|
||||
- MS Teams: conversation id (`19:[email protected]`) or `conversation:<id>` or `user:<aad-object-id>`
|
||||
|
||||
Name lookup:
|
||||
- For supported providers (Discord/Slack/etc), channel names like `Help` or `#help` are resolved via the directory cache.
|
||||
- On cache miss, Bot will attempt a live directory lookup when the provider supports it.
|
||||
|
||||
## Common flags
|
||||
|
||||
- `--channel <name>`
|
||||
- `--account <id>`
|
||||
- `--target <dest>` (target channel or user for send/poll/read/etc)
|
||||
- `--targets <name>` (repeat; broadcast only)
|
||||
- `--json`
|
||||
- `--dry-run`
|
||||
- `--verbose`
|
||||
|
||||
## Actions
|
||||
|
||||
### Core
|
||||
|
||||
- `send`
|
||||
- Channels: WhatsApp/Telegram/Discord/Google Chat/Slack/Mattermost (plugin)/Signal/iMessage/MS Teams
|
||||
- Required: `--target`, plus `--message` or `--media`
|
||||
- Optional: `--media`, `--reply-to`, `--thread-id`, `--gif-playback`
|
||||
- Telegram only: `--buttons` (requires `channels.telegram.capabilities.inlineButtons` to allow it)
|
||||
- Telegram only: `--thread-id` (forum topic id)
|
||||
- Slack only: `--thread-id` (thread timestamp; `--reply-to` uses the same field)
|
||||
- WhatsApp only: `--gif-playback`
|
||||
|
||||
- `poll`
|
||||
- Channels: WhatsApp/Discord/MS Teams
|
||||
- Required: `--target`, `--poll-question`, `--poll-option` (repeat)
|
||||
- Optional: `--poll-multi`
|
||||
- Discord only: `--poll-duration-hours`, `--message`
|
||||
|
||||
- `react`
|
||||
- Channels: Discord/Google Chat/Slack/Telegram/WhatsApp/Signal
|
||||
- Required: `--message-id`, `--target`
|
||||
- Optional: `--emoji`, `--remove`, `--participant`, `--from-me`, `--target-author`, `--target-author-uuid`
|
||||
- Note: `--remove` requires `--emoji` (omit `--emoji` to clear own reactions where supported; see /tools/reactions)
|
||||
- WhatsApp only: `--participant`, `--from-me`
|
||||
- Signal group reactions: `--target-author` or `--target-author-uuid` required
|
||||
|
||||
- `reactions`
|
||||
- Channels: Discord/Google Chat/Slack
|
||||
- Required: `--message-id`, `--target`
|
||||
- Optional: `--limit`
|
||||
|
||||
- `read`
|
||||
- Channels: Discord/Slack
|
||||
- Required: `--target`
|
||||
- Optional: `--limit`, `--before`, `--after`
|
||||
- Discord only: `--around`
|
||||
|
||||
- `edit`
|
||||
- Channels: Discord/Slack
|
||||
- Required: `--message-id`, `--message`, `--target`
|
||||
|
||||
- `delete`
|
||||
- Channels: Discord/Slack/Telegram
|
||||
- Required: `--message-id`, `--target`
|
||||
|
||||
- `pin` / `unpin`
|
||||
- Channels: Discord/Slack
|
||||
- Required: `--message-id`, `--target`
|
||||
|
||||
- `pins` (list)
|
||||
- Channels: Discord/Slack
|
||||
- Required: `--target`
|
||||
|
||||
- `permissions`
|
||||
- Channels: Discord
|
||||
- Required: `--target`
|
||||
|
||||
- `search`
|
||||
- Channels: Discord
|
||||
- Required: `--guild-id`, `--query`
|
||||
- Optional: `--channel-id`, `--channel-ids` (repeat), `--author-id`, `--author-ids` (repeat), `--limit`
|
||||
|
||||
### Threads
|
||||
|
||||
- `thread create`
|
||||
- Channels: Discord
|
||||
- Required: `--thread-name`, `--target` (channel id)
|
||||
- Optional: `--message-id`, `--auto-archive-min`
|
||||
|
||||
- `thread list`
|
||||
- Channels: Discord
|
||||
- Required: `--guild-id`
|
||||
- Optional: `--channel-id`, `--include-archived`, `--before`, `--limit`
|
||||
|
||||
- `thread reply`
|
||||
- Channels: Discord
|
||||
- Required: `--target` (thread id), `--message`
|
||||
- Optional: `--media`, `--reply-to`
|
||||
|
||||
### Emojis
|
||||
|
||||
- `emoji list`
|
||||
- Discord: `--guild-id`
|
||||
- Slack: no extra flags
|
||||
|
||||
- `emoji upload`
|
||||
- Channels: Discord
|
||||
- Required: `--guild-id`, `--emoji-name`, `--media`
|
||||
- Optional: `--role-ids` (repeat)
|
||||
|
||||
### Stickers
|
||||
|
||||
- `sticker send`
|
||||
- Channels: Discord
|
||||
- Required: `--target`, `--sticker-id` (repeat)
|
||||
- Optional: `--message`
|
||||
|
||||
- `sticker upload`
|
||||
- Channels: Discord
|
||||
- Required: `--guild-id`, `--sticker-name`, `--sticker-desc`, `--sticker-tags`, `--media`
|
||||
|
||||
### Roles / Channels / Members / Voice
|
||||
|
||||
- `role info` (Discord): `--guild-id`
|
||||
- `role add` / `role remove` (Discord): `--guild-id`, `--user-id`, `--role-id`
|
||||
- `channel info` (Discord): `--target`
|
||||
- `channel list` (Discord): `--guild-id`
|
||||
- `member info` (Discord/Slack): `--user-id` (+ `--guild-id` for Discord)
|
||||
- `voice status` (Discord): `--guild-id`, `--user-id`
|
||||
|
||||
### Events
|
||||
|
||||
- `event list` (Discord): `--guild-id`
|
||||
- `event create` (Discord): `--guild-id`, `--event-name`, `--start-time`
|
||||
- Optional: `--end-time`, `--desc`, `--channel-id`, `--location`, `--event-type`
|
||||
|
||||
### Moderation (Discord)
|
||||
|
||||
- `timeout`: `--guild-id`, `--user-id` (optional `--duration-min` or `--until`; omit both to clear timeout)
|
||||
- `kick`: `--guild-id`, `--user-id` (+ `--reason`)
|
||||
- `ban`: `--guild-id`, `--user-id` (+ `--delete-days`, `--reason`)
|
||||
- `timeout` also supports `--reason`
|
||||
|
||||
### Broadcast
|
||||
|
||||
- `broadcast`
|
||||
- Channels: any configured channel; use `--channel all` to target all providers
|
||||
- Required: `--targets` (repeat)
|
||||
- Optional: `--message`, `--media`, `--dry-run`
|
||||
|
||||
## Examples
|
||||
|
||||
Send a Discord reply:
|
||||
```
|
||||
bot message send --channel discord \
|
||||
--target channel:123 --message "hi" --reply-to 456
|
||||
```
|
||||
|
||||
Create a Discord poll:
|
||||
```
|
||||
bot message poll --channel discord \
|
||||
--target channel:123 \
|
||||
--poll-question "Snack?" \
|
||||
--poll-option Pizza --poll-option Sushi \
|
||||
--poll-multi --poll-duration-hours 48
|
||||
```
|
||||
|
||||
Send a Teams proactive message:
|
||||
```
|
||||
bot message send --channel msteams \
|
||||
--target conversation:19:[email protected] --message "hi"
|
||||
```
|
||||
|
||||
Create a Teams poll:
|
||||
```
|
||||
bot message poll --channel msteams \
|
||||
--target conversation:19:[email protected] \
|
||||
--poll-question "Lunch?" \
|
||||
--poll-option Pizza --poll-option Sushi
|
||||
```
|
||||
|
||||
React in Slack:
|
||||
```
|
||||
bot message react --channel slack \
|
||||
--target C123 --message-id 456 --emoji "✅"
|
||||
```
|
||||
|
||||
React in a Signal group:
|
||||
```
|
||||
bot message react --channel signal \
|
||||
--target signal:group:abc123 --message-id 1737630212345 \
|
||||
--emoji "✅" --target-author-uuid 123e4567-e89b-12d3-a456-426614174000
|
||||
```
|
||||
|
||||
Send Telegram inline buttons:
|
||||
```
|
||||
bot message send --channel telegram --target @mychat --message "Choose:" \
|
||||
--buttons '[ [{"text":"Yes","callback_data":"cmd:yes"}], [{"text":"No","callback_data":"cmd:no"}] ]'
|
||||
```
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"title": "CLI Reference",
|
||||
"pages": [
|
||||
"index",
|
||||
"acp",
|
||||
"agent",
|
||||
"agents",
|
||||
"approvals",
|
||||
"browser",
|
||||
"channels",
|
||||
"config",
|
||||
"configure",
|
||||
"cron",
|
||||
"dashboard",
|
||||
"devices",
|
||||
"directory",
|
||||
"dns",
|
||||
"docs",
|
||||
"doctor",
|
||||
"gateway",
|
||||
"health",
|
||||
"hooks",
|
||||
"logs",
|
||||
"memory",
|
||||
"message",
|
||||
"models",
|
||||
"node",
|
||||
"nodes",
|
||||
"onboard",
|
||||
"pairing",
|
||||
"plugins",
|
||||
"reset",
|
||||
"sandbox",
|
||||
"security",
|
||||
"sessions",
|
||||
"setup",
|
||||
"skills",
|
||||
"status",
|
||||
"system",
|
||||
"tui",
|
||||
"uninstall",
|
||||
"update",
|
||||
"voicecall",
|
||||
"webhooks"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: "`bot models`"
|
||||
description: "CLI reference for `bot models` (status/list/set/scan, aliases, fallbacks, auth)"
|
||||
---
|
||||
|
||||
Model discovery, scanning, and configuration (default model, fallbacks, auth profiles).
|
||||
|
||||
Related:
|
||||
- Providers + models: [Models](/providers/models)
|
||||
- Provider auth setup: [Getting started](/start/getting-started)
|
||||
|
||||
## Common commands
|
||||
|
||||
```bash
|
||||
bot models status
|
||||
bot models list
|
||||
bot models set <model-or-alias>
|
||||
bot models scan
|
||||
```
|
||||
|
||||
`bot models status` shows the resolved default/fallbacks plus an auth overview.
|
||||
When provider usage snapshots are available, the OAuth/token status section includes
|
||||
provider usage headers.
|
||||
Add `--probe` to run live auth probes against each configured provider profile.
|
||||
Probes are real requests (may consume tokens and trigger rate limits).
|
||||
|
||||
Notes:
|
||||
- `models set <model-or-alias>` accepts `provider/model` or an alias.
|
||||
- Model refs are parsed by splitting on the **first** `/`. If the model ID includes `/` (OpenRouter-style), include the provider prefix (example: `openrouter/moonshotai/kimi-k2`).
|
||||
- If you omit the provider, Bot treats the input as an alias or a model for the **default provider** (only works when there is no `/` in the model ID).
|
||||
|
||||
### `models status`
|
||||
Options:
|
||||
- `--json`
|
||||
- `--plain`
|
||||
- `--check` (exit 1=expired/missing, 2=expiring)
|
||||
- `--probe` (live probe of configured auth profiles)
|
||||
- `--probe-provider <name>` (probe one provider)
|
||||
- `--probe-profile <id>` (repeat or comma-separated profile ids)
|
||||
- `--probe-timeout <ms>`
|
||||
- `--probe-concurrency <n>`
|
||||
- `--probe-max-tokens <n>`
|
||||
|
||||
## Aliases + fallbacks
|
||||
|
||||
```bash
|
||||
bot models aliases list
|
||||
bot models fallbacks list
|
||||
```
|
||||
|
||||
## Auth profiles
|
||||
|
||||
```bash
|
||||
bot models auth add
|
||||
bot models auth login --provider <id>
|
||||
bot models auth setup-token
|
||||
bot models auth paste-token
|
||||
```
|
||||
`models auth login` runs a provider plugin’s auth flow (OAuth/API key). Use
|
||||
`bot plugins list` to see which providers are installed.
|
||||
|
||||
Notes:
|
||||
- `setup-token` runs `claude setup-token` on the current machine (requires the Claude Code CLI).
|
||||
- `paste-token` accepts a token string generated elsewhere.
|
||||
@@ -0,0 +1,104 @@
|
||||
---
|
||||
title: "`bot node`"
|
||||
description: "CLI reference for `bot node` (headless node host)"
|
||||
---
|
||||
|
||||
Run a **headless node host** that connects to the Gateway WebSocket and exposes
|
||||
`system.run` / `system.which` on this machine.
|
||||
|
||||
## Why use a node host?
|
||||
|
||||
Use a node host when you want agents to **run commands on other machines** in your
|
||||
network without installing a full macOS companion app there.
|
||||
|
||||
Common use cases:
|
||||
- Run commands on remote Linux/Windows boxes (build servers, lab machines, NAS).
|
||||
- Keep exec **sandboxed** on the gateway, but delegate approved runs to other hosts.
|
||||
- Provide a lightweight, headless execution target for automation or CI nodes.
|
||||
|
||||
Execution is still guarded by **exec approvals** and per‑agent allowlists on the
|
||||
node host, so you can keep command access scoped and explicit.
|
||||
|
||||
## Browser proxy (zero-config)
|
||||
|
||||
Node hosts automatically advertise a browser proxy if `browser.enabled` is not
|
||||
disabled on the node. This lets the agent use browser automation on that node
|
||||
without extra configuration.
|
||||
|
||||
Disable it on the node if needed:
|
||||
|
||||
```json5
|
||||
{
|
||||
nodeHost: {
|
||||
browserProxy: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Run (foreground)
|
||||
|
||||
```bash
|
||||
bot node run --host <gateway-host> --port 18789
|
||||
```
|
||||
|
||||
Options:
|
||||
- `--host <host>`: Gateway WebSocket host (default: `127.0.0.1`)
|
||||
- `--port <port>`: Gateway WebSocket port (default: `18789`)
|
||||
- `--tls`: Use TLS for the gateway connection
|
||||
- `--tls-fingerprint <sha256>`: Expected TLS certificate fingerprint (sha256)
|
||||
- `--node-id <id>`: Override node id (clears pairing token)
|
||||
- `--display-name <name>`: Override the node display name
|
||||
|
||||
## Service (background)
|
||||
|
||||
Install a headless node host as a user service.
|
||||
|
||||
```bash
|
||||
bot node install --host <gateway-host> --port 18789
|
||||
```
|
||||
|
||||
Options:
|
||||
- `--host <host>`: Gateway WebSocket host (default: `127.0.0.1`)
|
||||
- `--port <port>`: Gateway WebSocket port (default: `18789`)
|
||||
- `--tls`: Use TLS for the gateway connection
|
||||
- `--tls-fingerprint <sha256>`: Expected TLS certificate fingerprint (sha256)
|
||||
- `--node-id <id>`: Override node id (clears pairing token)
|
||||
- `--display-name <name>`: Override the node display name
|
||||
- `--runtime <runtime>`: Service runtime (`node` or `bun`)
|
||||
- `--force`: Reinstall/overwrite if already installed
|
||||
|
||||
Manage the service:
|
||||
|
||||
```bash
|
||||
bot node status
|
||||
bot node stop
|
||||
bot node restart
|
||||
bot node uninstall
|
||||
```
|
||||
|
||||
Use `bot node run` for a foreground node host (no service).
|
||||
|
||||
Service commands accept `--json` for machine-readable output.
|
||||
|
||||
## Pairing
|
||||
|
||||
The first connection creates a pending node pair request on the Gateway.
|
||||
Approve it via:
|
||||
|
||||
```bash
|
||||
bot nodes pending
|
||||
bot nodes approve <requestId>
|
||||
```
|
||||
|
||||
The node host stores its node id, token, display name, and gateway connection info in
|
||||
`~/.bot/node.json`.
|
||||
|
||||
## Exec approvals
|
||||
|
||||
`system.run` is gated by local exec approvals:
|
||||
|
||||
- `~/.bot/exec-approvals.json`
|
||||
- [Exec approvals](/tools/exec-approvals)
|
||||
- `bot approvals --node <id|name|ip>` (edit from the Gateway)
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: "`bot nodes`"
|
||||
description: "CLI reference for `bot nodes` (list/status/approve/invoke, camera/canvas/screen)"
|
||||
---
|
||||
|
||||
Manage paired nodes (devices) and invoke node capabilities.
|
||||
|
||||
Related:
|
||||
- Nodes overview: [Nodes](/nodes)
|
||||
- Camera: [Camera nodes](/nodes/camera)
|
||||
- Images: [Image nodes](/nodes/images)
|
||||
|
||||
Common options:
|
||||
- `--url`, `--token`, `--timeout`, `--json`
|
||||
|
||||
## Common commands
|
||||
|
||||
```bash
|
||||
bot nodes list
|
||||
bot nodes list --connected
|
||||
bot nodes list --last-connected 24h
|
||||
bot nodes pending
|
||||
bot nodes approve <requestId>
|
||||
bot nodes status
|
||||
bot nodes status --connected
|
||||
bot nodes status --last-connected 24h
|
||||
```
|
||||
|
||||
`nodes list` prints pending/paired tables. Paired rows include the most recent connect age (Last Connect).
|
||||
Use `--connected` to only show currently-connected nodes. Use `--last-connected <duration>` to
|
||||
filter to nodes that connected within a duration (e.g. `24h`, `7d`).
|
||||
|
||||
## Invoke / run
|
||||
|
||||
```bash
|
||||
bot nodes invoke --node <id|name|ip> --command <command> --params <json>
|
||||
bot nodes run --node <id|name|ip> <command...>
|
||||
bot nodes run --raw "git status"
|
||||
bot nodes run --agent main --node <id|name|ip> --raw "git status"
|
||||
```
|
||||
|
||||
Invoke flags:
|
||||
- `--params <json>`: JSON object string (default `{}`).
|
||||
- `--invoke-timeout <ms>`: node invoke timeout (default `15000`).
|
||||
- `--idempotency-key <key>`: optional idempotency key.
|
||||
|
||||
### Exec-style defaults
|
||||
|
||||
`nodes run` mirrors the model’s exec behavior (defaults + approvals):
|
||||
|
||||
- Reads `tools.exec.*` (plus `agents.list[].tools.exec.*` overrides).
|
||||
- Uses exec approvals (`exec.approval.request`) before invoking `system.run`.
|
||||
- `--node` can be omitted when `tools.exec.node` is set.
|
||||
- Requires a node that advertises `system.run` (macOS companion app or headless node host).
|
||||
|
||||
Flags:
|
||||
- `--cwd <path>`: working directory.
|
||||
- `--env <key=val>`: env override (repeatable).
|
||||
- `--command-timeout <ms>`: command timeout.
|
||||
- `--invoke-timeout <ms>`: node invoke timeout (default `30000`).
|
||||
- `--needs-screen-recording`: require screen recording permission.
|
||||
- `--raw <command>`: run a shell string (`/bin/sh -lc` or `cmd.exe /c`).
|
||||
- `--agent <id>`: agent-scoped approvals/allowlists (defaults to configured agent).
|
||||
- `--ask <off|on-miss|always>`, `--security <deny|allowlist|full>`: overrides.
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: "`bot onboard`"
|
||||
description: "CLI reference for `bot onboard` (interactive onboarding wizard)"
|
||||
---
|
||||
|
||||
Interactive onboarding wizard (local or remote Gateway setup).
|
||||
|
||||
Related:
|
||||
- Wizard guide: [Onboarding](/start/onboarding)
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
bot onboard
|
||||
bot onboard --flow quickstart
|
||||
bot onboard --flow manual
|
||||
bot onboard --mode remote --remote-url ws://gateway-host:18789
|
||||
```
|
||||
|
||||
Flow notes:
|
||||
- `quickstart`: minimal prompts, auto-generates a gateway token.
|
||||
- `manual`: full prompts for port/bind/auth (alias of `advanced`).
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "`bot pairing`"
|
||||
description: "CLI reference for `bot pairing` (approve/list pairing requests)"
|
||||
---
|
||||
|
||||
Approve or inspect DM pairing requests (for channels that support pairing).
|
||||
|
||||
Related:
|
||||
- Pairing flow: [Pairing](/start/pairing)
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
bot pairing list whatsapp
|
||||
bot pairing approve whatsapp <code> --notify
|
||||
```
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: "`bot plugins`"
|
||||
description: "CLI reference for `bot plugins` (list, install, enable/disable, doctor)"
|
||||
---
|
||||
|
||||
Manage Gateway plugins/extensions (loaded in-process).
|
||||
|
||||
Related:
|
||||
- Plugin system: [Plugins](/plugin)
|
||||
- Plugin manifest + schema: [Plugin manifest](/plugins/manifest)
|
||||
- Security hardening: [Security](/gateway/security)
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
bot plugins list
|
||||
bot plugins info <id>
|
||||
bot plugins enable <id>
|
||||
bot plugins disable <id>
|
||||
bot plugins doctor
|
||||
bot plugins update <id>
|
||||
bot plugins update --all
|
||||
```
|
||||
|
||||
Bundled plugins ship with Bot but start disabled. Use `plugins enable` to
|
||||
activate them.
|
||||
|
||||
All plugins must ship a `bot.plugin.json` file with an inline JSON Schema
|
||||
(`configSchema`, even if empty). Missing/invalid manifests or schemas prevent
|
||||
the plugin from loading and fail config validation.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
bot plugins install <path-or-spec>
|
||||
```
|
||||
|
||||
Security note: treat plugin installs like running code. Prefer pinned versions.
|
||||
|
||||
Supported archives: `.zip`, `.tgz`, `.tar.gz`, `.tar`.
|
||||
|
||||
Use `--link` to avoid copying a local directory (adds to `plugins.load.paths`):
|
||||
|
||||
```bash
|
||||
bot plugins install -l ./my-plugin
|
||||
```
|
||||
|
||||
### Update
|
||||
|
||||
```bash
|
||||
bot plugins update <id>
|
||||
bot plugins update --all
|
||||
bot plugins update <id> --dry-run
|
||||
```
|
||||
|
||||
Updates only apply to plugins installed from npm (tracked in `plugins.installs`).
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: "`bot reset`"
|
||||
description: "CLI reference for `bot reset` (reset local state/config)"
|
||||
---
|
||||
|
||||
Reset local config/state (keeps the CLI installed).
|
||||
|
||||
```bash
|
||||
bot reset
|
||||
bot reset --dry-run
|
||||
bot reset --scope config+creds+sessions --yes --non-interactive
|
||||
```
|
||||
@@ -0,0 +1,146 @@
|
||||
---
|
||||
title: "Sandbox CLI"
|
||||
description: "Manage sandbox containers and inspect effective sandbox policy"
|
||||
---
|
||||
|
||||
Manage Docker-based sandbox containers for isolated agent execution.
|
||||
|
||||
## Overview
|
||||
|
||||
Bot can run agents in isolated Docker containers for security. The `sandbox` commands help you manage these containers, especially after updates or configuration changes.
|
||||
|
||||
## Commands
|
||||
|
||||
### `bot sandbox explain`
|
||||
|
||||
Inspect the **effective** sandbox mode/scope/workspace access, sandbox tool policy, and elevated gates (with fix-it config key paths).
|
||||
|
||||
```bash
|
||||
bot sandbox explain
|
||||
bot sandbox explain --session agent:main:main
|
||||
bot sandbox explain --agent work
|
||||
bot sandbox explain --json
|
||||
```
|
||||
|
||||
### `bot sandbox list`
|
||||
|
||||
List all sandbox containers with their status and configuration.
|
||||
|
||||
```bash
|
||||
bot sandbox list
|
||||
bot sandbox list --browser # List only browser containers
|
||||
bot sandbox list --json # JSON output
|
||||
```
|
||||
|
||||
**Output includes:**
|
||||
- Container name and status (running/stopped)
|
||||
- Docker image and whether it matches config
|
||||
- Age (time since creation)
|
||||
- Idle time (time since last use)
|
||||
- Associated session/agent
|
||||
|
||||
### `bot sandbox recreate`
|
||||
|
||||
Remove sandbox containers to force recreation with updated images/config.
|
||||
|
||||
```bash
|
||||
bot sandbox recreate --all # Recreate all containers
|
||||
bot sandbox recreate --session main # Specific session
|
||||
bot sandbox recreate --agent mybot # Specific agent
|
||||
bot sandbox recreate --browser # Only browser containers
|
||||
bot sandbox recreate --all --force # Skip confirmation
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--all`: Recreate all sandbox containers
|
||||
- `--session <key>`: Recreate container for specific session
|
||||
- `--agent <id>`: Recreate containers for specific agent
|
||||
- `--browser`: Only recreate browser containers
|
||||
- `--force`: Skip confirmation prompt
|
||||
|
||||
**Important:** Containers are automatically recreated when the agent is next used.
|
||||
|
||||
## Use Cases
|
||||
|
||||
### After updating Docker images
|
||||
|
||||
```bash
|
||||
# Pull new image
|
||||
docker pull bot-sandbox:latest
|
||||
docker tag bot-sandbox:latest bot-sandbox:bookworm-slim
|
||||
|
||||
# Update config to use new image
|
||||
# Edit config: agents.defaults.sandbox.docker.image (or agents.list[].sandbox.docker.image)
|
||||
|
||||
# Recreate containers
|
||||
bot sandbox recreate --all
|
||||
```
|
||||
|
||||
### After changing sandbox configuration
|
||||
|
||||
```bash
|
||||
# Edit config: agents.defaults.sandbox.* (or agents.list[].sandbox.*)
|
||||
|
||||
# Recreate to apply new config
|
||||
bot sandbox recreate --all
|
||||
```
|
||||
|
||||
### After changing setupCommand
|
||||
|
||||
```bash
|
||||
bot sandbox recreate --all
|
||||
# or just one agent:
|
||||
bot sandbox recreate --agent family
|
||||
```
|
||||
|
||||
|
||||
### For a specific agent only
|
||||
|
||||
```bash
|
||||
# Update only one agent's containers
|
||||
bot sandbox recreate --agent alfred
|
||||
```
|
||||
|
||||
## Why is this needed?
|
||||
|
||||
**Problem:** When you update sandbox Docker images or configuration:
|
||||
- Existing containers continue running with old settings
|
||||
- Containers are only pruned after 24h of inactivity
|
||||
- Regularly-used agents keep old containers running indefinitely
|
||||
|
||||
**Solution:** Use `bot sandbox recreate` to force removal of old containers. They'll be recreated automatically with current settings when next needed.
|
||||
|
||||
Tip: prefer `bot sandbox recreate` over manual `docker rm`. It uses the
|
||||
Gateway’s container naming and avoids mismatches when scope/session keys change.
|
||||
|
||||
## Configuration
|
||||
|
||||
Sandbox settings live in `~/.bot/bot.json` under `agents.defaults.sandbox` (per-agent overrides go in `agents.list[].sandbox`):
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"sandbox": {
|
||||
"mode": "all", // off, non-main, all
|
||||
"scope": "agent", // session, agent, shared
|
||||
"docker": {
|
||||
"image": "bot-sandbox:bookworm-slim",
|
||||
"containerPrefix": "bot-sbx-"
|
||||
// ... more Docker options
|
||||
},
|
||||
"prune": {
|
||||
"idleHours": 24, // Auto-prune after 24h idle
|
||||
"maxAgeDays": 7 // Auto-prune after 7 days
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [Sandbox Documentation](/gateway/sandboxing)
|
||||
- [Agent Configuration](/concepts/agent-workspace)
|
||||
- [Doctor Command](/gateway/doctor) - Check sandbox setup
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: "`bot security`"
|
||||
description: "CLI reference for `bot security` (audit and fix common security footguns)"
|
||||
---
|
||||
|
||||
Security tools (audit + optional fixes).
|
||||
|
||||
Related:
|
||||
- Security guide: [Security](/gateway/security)
|
||||
|
||||
## Audit
|
||||
|
||||
```bash
|
||||
bot security audit
|
||||
bot security audit --deep
|
||||
bot security audit --fix
|
||||
```
|
||||
|
||||
The audit warns when multiple DM senders share the main session and recommends `session.dmScope="per-channel-peer"` for shared inboxes.
|
||||
It also warns when small models (`<=300B`) are used without sandboxing and with web/browser tools enabled.
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: "`bot sessions`"
|
||||
description: "CLI reference for `bot sessions` (list stored sessions + usage)"
|
||||
---
|
||||
|
||||
List stored conversation sessions.
|
||||
|
||||
```bash
|
||||
bot sessions
|
||||
bot sessions --active 120
|
||||
bot sessions --json
|
||||
```
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "`bot setup`"
|
||||
description: "CLI reference for `bot setup` (initialize config + workspace)"
|
||||
---
|
||||
|
||||
Initialize `~/.bot/bot.json` and the agent workspace.
|
||||
|
||||
Related:
|
||||
- Getting started: [Getting started](/start/getting-started)
|
||||
- Wizard: [Onboarding](/start/onboarding)
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
bot setup
|
||||
bot setup --workspace ~/bot
|
||||
```
|
||||
|
||||
To run the wizard via setup:
|
||||
|
||||
```bash
|
||||
bot setup --wizard
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: "`bot skills`"
|
||||
description: "CLI reference for `bot skills` (list/info/check) and skill eligibility"
|
||||
---
|
||||
|
||||
Inspect skills (bundled + workspace + managed overrides) and see what’s eligible vs missing requirements.
|
||||
|
||||
Related:
|
||||
- Skills system: [Skills](/tools/skills)
|
||||
- Skills config: [Skills config](/tools/skills-config)
|
||||
- Skills installs: [Skills](/tools/skills)
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
bot skills list
|
||||
bot skills list --eligible
|
||||
bot skills info <name>
|
||||
bot skills check
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: "`bot status`"
|
||||
description: "CLI reference for `bot status` (diagnostics, probes, usage snapshots)"
|
||||
---
|
||||
|
||||
Diagnostics for channels + sessions.
|
||||
|
||||
```bash
|
||||
bot status
|
||||
bot status --all
|
||||
bot status --deep
|
||||
bot status --usage
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `--deep` runs live probes (WhatsApp Web + Telegram + Discord + Google Chat + Slack + Signal).
|
||||
- Output includes per-agent session stores when multiple agents are configured.
|
||||
- Overview includes Gateway + node host service install/runtime status when available.
|
||||
- Overview includes update channel + git SHA (for source checkouts).
|
||||
- Update info surfaces in the Overview; if an update is available, status prints a hint to run `bot update` (see [Updating](/install/updating)).
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: "`bot system`"
|
||||
description: "CLI reference for `bot system` (system events, heartbeat, presence)"
|
||||
---
|
||||
|
||||
System-level helpers for the Gateway: enqueue system events, control heartbeats,
|
||||
and view presence.
|
||||
|
||||
## Common commands
|
||||
|
||||
```bash
|
||||
bot system event --text "Check for urgent follow-ups" --mode now
|
||||
bot system heartbeat enable
|
||||
bot system heartbeat last
|
||||
bot system presence
|
||||
```
|
||||
|
||||
## `system event`
|
||||
|
||||
Enqueue a system event on the **main** session. The next heartbeat will inject
|
||||
it as a `System:` line in the prompt. Use `--mode now` to trigger the heartbeat
|
||||
immediately; `next-heartbeat` waits for the next scheduled tick.
|
||||
|
||||
Flags:
|
||||
- `--text <text>`: required system event text.
|
||||
- `--mode <mode>`: `now` or `next-heartbeat` (default).
|
||||
- `--json`: machine-readable output.
|
||||
|
||||
## `system heartbeat last|enable|disable`
|
||||
|
||||
Heartbeat controls:
|
||||
- `last`: show the last heartbeat event.
|
||||
- `enable`: turn heartbeats back on (use this if they were disabled).
|
||||
- `disable`: pause heartbeats.
|
||||
|
||||
Flags:
|
||||
- `--json`: machine-readable output.
|
||||
|
||||
## `system presence`
|
||||
|
||||
List the current system presence entries the Gateway knows about (nodes,
|
||||
instances, and similar status lines).
|
||||
|
||||
Flags:
|
||||
- `--json`: machine-readable output.
|
||||
|
||||
## Notes
|
||||
|
||||
- Requires a running Gateway reachable by your current config (local or remote).
|
||||
- System events are ephemeral and not persisted across restarts.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: "`bot tui`"
|
||||
description: "CLI reference for `bot tui` (terminal UI connected to the Gateway)"
|
||||
---
|
||||
|
||||
Open the terminal UI connected to the Gateway.
|
||||
|
||||
Related:
|
||||
- TUI guide: [TUI](/tui)
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
bot tui
|
||||
bot tui --url ws://127.0.0.1:18789 --token <token>
|
||||
bot tui --session main --deliver
|
||||
```
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: "`bot uninstall`"
|
||||
description: "CLI reference for `bot uninstall` (remove gateway service + local data)"
|
||||
---
|
||||
|
||||
Uninstall the gateway service + local data (CLI remains).
|
||||
|
||||
```bash
|
||||
bot uninstall
|
||||
bot uninstall --all --yes
|
||||
bot uninstall --dry-run
|
||||
```
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
title: "`bot update`"
|
||||
description: "CLI reference for `bot update` (safe-ish source update + gateway auto-restart)"
|
||||
---
|
||||
|
||||
Safely update Bot and switch between stable/beta/dev channels.
|
||||
|
||||
If you installed via **npm/pnpm** (global install, no git metadata), updates happen via the package manager flow in [Updating](/install/updating).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
bot update
|
||||
bot update status
|
||||
bot update wizard
|
||||
bot update --channel beta
|
||||
bot update --channel dev
|
||||
bot update --tag beta
|
||||
bot update --no-restart
|
||||
bot update --json
|
||||
bot --update
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
- `--no-restart`: skip restarting the Gateway service after a successful update.
|
||||
- `--channel <stable|beta|dev>`: set the update channel (git + npm; persisted in config).
|
||||
- `--tag <dist-tag|version>`: override the npm dist-tag or version for this update only.
|
||||
- `--json`: print machine-readable `UpdateRunResult` JSON.
|
||||
- `--timeout <seconds>`: per-step timeout (default is 1200s).
|
||||
|
||||
Note: downgrades require confirmation because older versions can break configuration.
|
||||
|
||||
## `update status`
|
||||
|
||||
Show the active update channel + git tag/branch/SHA (for source checkouts), plus update availability.
|
||||
|
||||
```bash
|
||||
bot update status
|
||||
bot update status --json
|
||||
bot update status --timeout 10
|
||||
```
|
||||
|
||||
Options:
|
||||
- `--json`: print machine-readable status JSON.
|
||||
- `--timeout <seconds>`: timeout for checks (default is 3s).
|
||||
|
||||
## `update wizard`
|
||||
|
||||
Interactive flow to pick an update channel and confirm whether to restart the Gateway
|
||||
after updating (default is to restart). If you select `dev` without a git checkout, it
|
||||
offers to create one.
|
||||
|
||||
## What it does
|
||||
|
||||
When you switch channels explicitly (`--channel ...`), Bot also keeps the
|
||||
install method aligned:
|
||||
|
||||
- `dev` → ensures a git checkout (default: `~/bot`, override with `BOT_GIT_DIR`),
|
||||
updates it, and installs the global CLI from that checkout.
|
||||
- `stable`/`beta` → installs from npm using the matching dist-tag.
|
||||
|
||||
## Git checkout flow
|
||||
|
||||
Channels:
|
||||
|
||||
- `stable`: checkout the latest non-beta tag, then build + doctor.
|
||||
- `beta`: checkout the latest `-beta` tag, then build + doctor.
|
||||
- `dev`: checkout `main`, then fetch + rebase.
|
||||
|
||||
High-level:
|
||||
|
||||
1. Requires a clean worktree (no uncommitted changes).
|
||||
2. Switches to the selected channel (tag or branch).
|
||||
3. Fetches upstream (dev only).
|
||||
4. Dev only: preflight lint + TypeScript build in a temp worktree; if the tip fails, walks back up to 10 commits to find the newest clean build.
|
||||
5. Rebases onto the selected commit (dev only).
|
||||
6. Installs deps (pnpm preferred; npm fallback).
|
||||
7. Builds + builds the Control UI.
|
||||
8. Runs `bot doctor` as the final “safe update” check.
|
||||
9. Syncs plugins to the active channel (dev uses bundled extensions; stable/beta uses npm) and updates npm-installed plugins.
|
||||
|
||||
## `--update` shorthand
|
||||
|
||||
`bot --update` rewrites to `bot update` (useful for shells and launcher scripts).
|
||||
|
||||
## See also
|
||||
|
||||
- `bot doctor` (offers to run update first on git checkouts)
|
||||
- [Development channels](/install/development-channels)
|
||||
- [Updating](/install/updating)
|
||||
- [CLI reference](/cli)
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: "`bot voicecall`"
|
||||
description: "CLI reference for `bot voicecall` (voice-call plugin command surface)"
|
||||
---
|
||||
|
||||
`voicecall` is a plugin-provided command. It only appears if the voice-call plugin is installed and enabled.
|
||||
|
||||
Primary doc:
|
||||
- Voice-call plugin: [Voice Call](/plugins/voice-call)
|
||||
|
||||
## Common commands
|
||||
|
||||
```bash
|
||||
bot voicecall status --call-id <id>
|
||||
bot voicecall call --to "+15555550123" --message "Hello" --mode notify
|
||||
bot voicecall continue --call-id <id> --message "Any questions?"
|
||||
bot voicecall end --call-id <id>
|
||||
```
|
||||
|
||||
## Exposing webhooks (Tailscale)
|
||||
|
||||
```bash
|
||||
bot voicecall expose --mode serve
|
||||
bot voicecall expose --mode funnel
|
||||
bot voicecall unexpose
|
||||
```
|
||||
|
||||
Security note: only expose the webhook endpoint to networks you trust. Prefer Tailscale Serve over Funnel when possible.
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: "`bot webhooks`"
|
||||
description: "CLI reference for `bot webhooks` (webhook helpers + Gmail Pub/Sub)"
|
||||
---
|
||||
|
||||
Webhook helpers and integrations (Gmail Pub/Sub, webhook helpers).
|
||||
|
||||
Related:
|
||||
- Webhooks: [Webhook](/automation/webhook)
|
||||
- Gmail Pub/Sub: [Gmail Pub/Sub](/automation/gmail-pubsub)
|
||||
|
||||
## Gmail
|
||||
|
||||
```bash
|
||||
bot webhooks gmail setup --account [email protected]
|
||||
bot webhooks gmail run
|
||||
```
|
||||
|
||||
See [Gmail Pub/Sub documentation](/automation/gmail-pubsub) for details.
|
||||
@@ -0,0 +1,124 @@
|
||||
---
|
||||
title: "Agent Loop (Bot)"
|
||||
description: "Agent loop lifecycle, streams, and wait semantics"
|
||||
---
|
||||
|
||||
An agentic loop is the full “real” run of an agent: intake → context assembly → model inference →
|
||||
tool execution → streaming replies → persistence. It’s the authoritative path that turns a message
|
||||
into actions and a final reply, while keeping session state consistent.
|
||||
|
||||
In Bot, a loop is a single, serialized run per session that emits lifecycle and stream events
|
||||
as the model thinks, calls tools, and streams output. This doc explains how that authentic loop is
|
||||
wired end-to-end.
|
||||
|
||||
## Entry points
|
||||
- Gateway RPC: `agent` and `agent.wait`.
|
||||
- CLI: `agent` command.
|
||||
|
||||
## How it works (high-level)
|
||||
1) `agent` RPC validates params, resolves session (sessionKey/sessionId), persists session metadata, returns `{ runId, acceptedAt }` immediately.
|
||||
2) `agentCommand` runs the agent:
|
||||
- resolves model + thinking/verbose defaults
|
||||
- loads skills snapshot
|
||||
- calls `runEmbeddedPiAgent` (pi-agent-core runtime)
|
||||
- emits **lifecycle end/error** if the embedded loop does not emit one
|
||||
3) `runEmbeddedPiAgent`:
|
||||
- serializes runs via per-session + global queues
|
||||
- resolves model + auth profile and builds the pi session
|
||||
- subscribes to pi events and streams assistant/tool deltas
|
||||
- enforces timeout -> aborts run if exceeded
|
||||
- returns payloads + usage metadata
|
||||
4) `subscribeEmbeddedPiSession` bridges pi-agent-core events to Bot `agent` stream:
|
||||
- tool events => `stream: "tool"`
|
||||
- assistant deltas => `stream: "assistant"`
|
||||
- lifecycle events => `stream: "lifecycle"` (`phase: "start" | "end" | "error"`)
|
||||
5) `agent.wait` uses `waitForAgentJob`:
|
||||
- waits for **lifecycle end/error** for `runId`
|
||||
- returns `{ status: ok|error|timeout, startedAt, endedAt, error? }`
|
||||
|
||||
## Queueing + concurrency
|
||||
- Runs are serialized per session key (session lane) and optionally through a global lane.
|
||||
- This prevents tool/session races and keeps session history consistent.
|
||||
- Messaging channels can choose queue modes (collect/steer/followup) that feed this lane system.
|
||||
See [Command Queue](/concepts/queue).
|
||||
|
||||
## Session + workspace preparation
|
||||
- Workspace is resolved and created; sandboxed runs may redirect to a sandbox workspace root.
|
||||
- Skills are loaded (or reused from a snapshot) and injected into env and prompt.
|
||||
- Bootstrap/context files are resolved and injected into the system prompt report.
|
||||
- A session write lock is acquired; `SessionManager` is opened and prepared before streaming.
|
||||
|
||||
## Prompt assembly + system prompt
|
||||
- System prompt is built from Bot’s base prompt, skills prompt, bootstrap context, and per-run overrides.
|
||||
- Model-specific limits and compaction reserve tokens are enforced.
|
||||
- See [System prompt](/concepts/system-prompt) for what the model sees.
|
||||
|
||||
## Hook points (where you can intercept)
|
||||
Bot has two hook systems:
|
||||
- **Internal hooks** (Gateway hooks): event-driven scripts for commands and lifecycle events.
|
||||
- **Plugin hooks**: extension points inside the agent/tool lifecycle and gateway pipeline.
|
||||
|
||||
### Internal hooks (Gateway hooks)
|
||||
- **`agent:bootstrap`**: runs while building bootstrap files before the system prompt is finalized.
|
||||
Use this to add/remove bootstrap context files.
|
||||
- **Command hooks**: `/new`, `/reset`, `/stop`, and other command events (see Hooks doc).
|
||||
|
||||
See [Hooks](/hooks) for setup and examples.
|
||||
|
||||
### Plugin hooks (agent + gateway lifecycle)
|
||||
These run inside the agent loop or gateway pipeline:
|
||||
- **`before_agent_start`**: inject context or override system prompt before the run starts.
|
||||
- **`agent_end`**: inspect the final message list and run metadata after completion.
|
||||
- **`before_compaction` / `after_compaction`**: observe or annotate compaction cycles.
|
||||
- **`before_tool_call` / `after_tool_call`**: intercept tool params/results.
|
||||
- **`tool_result_persist`**: synchronously transform tool results before they are written to the session transcript.
|
||||
- **`message_received` / `message_sending` / `message_sent`**: inbound + outbound message hooks.
|
||||
- **`session_start` / `session_end`**: session lifecycle boundaries.
|
||||
- **`gateway_start` / `gateway_stop`**: gateway lifecycle events.
|
||||
|
||||
See [Plugins](/plugin#plugin-hooks) for the hook API and registration details.
|
||||
|
||||
## Streaming + partial replies
|
||||
- Assistant deltas are streamed from pi-agent-core and emitted as `assistant` events.
|
||||
- Block streaming can emit partial replies either on `text_end` or `message_end`.
|
||||
- Reasoning streaming can be emitted as a separate stream or as block replies.
|
||||
- See [Streaming](/concepts/streaming) for chunking and block reply behavior.
|
||||
|
||||
## Tool execution + messaging tools
|
||||
- Tool start/update/end events are emitted on the `tool` stream.
|
||||
- Tool results are sanitized for size and image payloads before logging/emitting.
|
||||
- Messaging tool sends are tracked to suppress duplicate assistant confirmations.
|
||||
|
||||
## Reply shaping + suppression
|
||||
- Final payloads are assembled from:
|
||||
- assistant text (and optional reasoning)
|
||||
- inline tool summaries (when verbose + allowed)
|
||||
- assistant error text when the model errors
|
||||
- `NO_REPLY` is treated as a silent token and filtered from outgoing payloads.
|
||||
- Messaging tool duplicates are removed from the final payload list.
|
||||
- If no renderable payloads remain and a tool errored, a fallback tool error reply is emitted
|
||||
(unless a messaging tool already sent a user-visible reply).
|
||||
|
||||
## Compaction + retries
|
||||
- Auto-compaction emits `compaction` stream events and can trigger a retry.
|
||||
- On retry, in-memory buffers and tool summaries are reset to avoid duplicate output.
|
||||
- See [Compaction](/concepts/compaction) for the compaction pipeline.
|
||||
|
||||
## Event streams (today)
|
||||
- `lifecycle`: emitted by `subscribeEmbeddedPiSession` (and as a fallback by `agentCommand`)
|
||||
- `assistant`: streamed deltas from pi-agent-core
|
||||
- `tool`: streamed tool events from pi-agent-core
|
||||
|
||||
## Chat channel handling
|
||||
- Assistant deltas are buffered into chat `delta` messages.
|
||||
- A chat `final` is emitted on **lifecycle end/error**.
|
||||
|
||||
## Timeouts
|
||||
- `agent.wait` default: 30s (just the wait). `timeoutMs` param overrides.
|
||||
- Agent runtime: `agents.defaults.timeoutSeconds` default 600s; enforced in `runEmbeddedPiAgent` abort timer.
|
||||
|
||||
## Where things can end early
|
||||
- Agent timeout (abort)
|
||||
- AbortSignal (cancel)
|
||||
- Gateway disconnect or RPC timeout
|
||||
- `agent.wait` timeout (wait-only, does not stop agent)
|
||||
@@ -0,0 +1,228 @@
|
||||
---
|
||||
title: "Agent workspace"
|
||||
description: "Agent workspace: location, layout, and backup strategy"
|
||||
---
|
||||
|
||||
The workspace is the agent's home. It is the only working directory used for
|
||||
file tools and for workspace context. Keep it private and treat it as memory.
|
||||
|
||||
This is separate from `~/.bot/`, which stores config, credentials, and
|
||||
sessions.
|
||||
|
||||
**Important:** the workspace is the **default cwd**, not a hard sandbox. Tools
|
||||
resolve relative paths against the workspace, but absolute paths can still reach
|
||||
elsewhere on the host unless sandboxing is enabled. If you need isolation, use
|
||||
[`agents.defaults.sandbox`](/gateway/sandboxing) (and/or per‑agent sandbox config).
|
||||
When sandboxing is enabled and `workspaceAccess` is not `"rw"`, tools operate
|
||||
inside a sandbox workspace under `~/.bot/sandboxes`, not your host workspace.
|
||||
|
||||
## Default location
|
||||
|
||||
- Default: `~/bot`
|
||||
- If `BOT_PROFILE` is set and not `"default"`, the default becomes
|
||||
`~/bot-<profile>`.
|
||||
- Override in `~/.bot/bot.json`:
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
workspace: "~/bot"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`bot onboard`, `bot configure`, or `bot setup` will create the
|
||||
workspace and seed the bootstrap files if they are missing.
|
||||
|
||||
If you already manage the workspace files yourself, you can disable bootstrap
|
||||
file creation:
|
||||
|
||||
```json5
|
||||
{ agent: { skipBootstrap: true } }
|
||||
```
|
||||
|
||||
## Extra workspace folders
|
||||
|
||||
Older installs may have created `~/bot`. Keeping multiple workspace
|
||||
directories around can cause confusing auth or state drift, because only one
|
||||
workspace is active at a time.
|
||||
|
||||
**Recommendation:** keep a single active workspace. If you no longer use the
|
||||
extra folders, archive or move them to Trash (for example `trash ~/bot`).
|
||||
If you intentionally keep multiple workspaces, make sure
|
||||
`agents.defaults.workspace` points to the active one.
|
||||
|
||||
`bot doctor` warns when it detects extra workspace directories.
|
||||
|
||||
## Workspace file map (what each file means)
|
||||
|
||||
These are the standard files Bot expects inside the workspace:
|
||||
|
||||
- `AGENTS.md`
|
||||
- Operating instructions for the agent and how it should use memory.
|
||||
- Loaded at the start of every session.
|
||||
- Good place for rules, priorities, and "how to behave" details.
|
||||
|
||||
- `SOUL.md`
|
||||
- Persona, tone, and boundaries.
|
||||
- Loaded every session.
|
||||
|
||||
- `USER.md`
|
||||
- Who the user is and how to address them.
|
||||
- Loaded every session.
|
||||
|
||||
- `IDENTITY.md`
|
||||
- The agent's name, vibe, and emoji.
|
||||
- Created/updated during the bootstrap ritual.
|
||||
|
||||
- `TOOLS.md`
|
||||
- Notes about your local tools and conventions.
|
||||
- Does not control tool availability; it is only guidance.
|
||||
|
||||
- `HEARTBEAT.md`
|
||||
- Optional tiny checklist for heartbeat runs.
|
||||
- Keep it short to avoid token burn.
|
||||
|
||||
- `BOOT.md`
|
||||
- Optional startup checklist executed on gateway restart when internal hooks are enabled.
|
||||
- Keep it short; use the message tool for outbound sends.
|
||||
|
||||
- `BOOTSTRAP.md`
|
||||
- One-time first-run ritual.
|
||||
- Only created for a brand-new workspace.
|
||||
- Delete it after the ritual is complete.
|
||||
|
||||
- `memory/YYYY-MM-DD.md`
|
||||
- Daily memory log (one file per day).
|
||||
- Recommended to read today + yesterday on session start.
|
||||
|
||||
- `MEMORY.md` (optional)
|
||||
- Curated long-term memory.
|
||||
- Only load in the main, private session (not shared/group contexts).
|
||||
|
||||
See [Memory](/concepts/memory) for the workflow and automatic memory flush.
|
||||
|
||||
- `skills/` (optional)
|
||||
- Workspace-specific skills.
|
||||
- Overrides managed/bundled skills when names collide.
|
||||
|
||||
- `canvas/` (optional)
|
||||
- Canvas UI files for node displays (for example `canvas/index.html`).
|
||||
|
||||
If any bootstrap file is missing, Bot injects a "missing file" marker into
|
||||
the session and continues. Large bootstrap files are truncated when injected;
|
||||
adjust the limit with `agents.defaults.bootstrapMaxChars` (default: 20000).
|
||||
`bot setup` can recreate missing defaults without overwriting existing
|
||||
files.
|
||||
|
||||
## What is NOT in the workspace
|
||||
|
||||
These live under `~/.bot/` and should NOT be committed to the workspace repo:
|
||||
|
||||
- `~/.bot/bot.json` (config)
|
||||
- `~/.bot/credentials/` (OAuth tokens, API keys)
|
||||
- `~/.bot/agents/<agentId>/sessions/` (session transcripts + metadata)
|
||||
- `~/.bot/skills/` (managed skills)
|
||||
|
||||
If you need to migrate sessions or config, copy them separately and keep them
|
||||
out of version control.
|
||||
|
||||
## Git backup (recommended, private)
|
||||
|
||||
Treat the workspace as private memory. Put it in a **private** git repo so it is
|
||||
backed up and recoverable.
|
||||
|
||||
Run these steps on the machine where the Gateway runs (that is where the
|
||||
workspace lives).
|
||||
|
||||
### 1) Initialize the repo
|
||||
|
||||
If git is installed, brand-new workspaces are initialized automatically. If this
|
||||
workspace is not already a repo, run:
|
||||
|
||||
```bash
|
||||
cd ~/bot
|
||||
git init
|
||||
git add AGENTS.md SOUL.md TOOLS.md IDENTITY.md USER.md HEARTBEAT.md memory/
|
||||
git commit -m "Add agent workspace"
|
||||
```
|
||||
|
||||
### 2) Add a private remote (beginner-friendly options)
|
||||
|
||||
Option A: GitHub web UI
|
||||
|
||||
1. Create a new **private** repository on GitHub.
|
||||
2. Do not initialize with a README (avoids merge conflicts).
|
||||
3. Copy the HTTPS remote URL.
|
||||
4. Add the remote and push:
|
||||
|
||||
```bash
|
||||
git branch -M main
|
||||
git remote add origin <https-url>
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
Option B: GitHub CLI (`gh`)
|
||||
|
||||
```bash
|
||||
gh auth login
|
||||
gh repo create bot-workspace --private --source . --remote origin --push
|
||||
```
|
||||
|
||||
Option C: GitLab web UI
|
||||
|
||||
1. Create a new **private** repository on GitLab.
|
||||
2. Do not initialize with a README (avoids merge conflicts).
|
||||
3. Copy the HTTPS remote URL.
|
||||
4. Add the remote and push:
|
||||
|
||||
```bash
|
||||
git branch -M main
|
||||
git remote add origin <https-url>
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
### 3) Ongoing updates
|
||||
|
||||
```bash
|
||||
git status
|
||||
git add .
|
||||
git commit -m "Update memory"
|
||||
git push
|
||||
```
|
||||
|
||||
## Do not commit secrets
|
||||
|
||||
Even in a private repo, avoid storing secrets in the workspace:
|
||||
|
||||
- API keys, OAuth tokens, passwords, or private credentials.
|
||||
- Anything under `~/.bot/`.
|
||||
- Raw dumps of chats or sensitive attachments.
|
||||
|
||||
If you must store sensitive references, use placeholders and keep the real
|
||||
secret elsewhere (password manager, environment variables, or `~/.bot/`).
|
||||
|
||||
Suggested `.gitignore` starter:
|
||||
|
||||
```text
|
||||
.DS_Store
|
||||
.env
|
||||
**/*.key
|
||||
**/*.pem
|
||||
**/secrets*
|
||||
```
|
||||
|
||||
## Moving the workspace to a new machine
|
||||
|
||||
1. Clone the repo to the desired path (default `~/bot`).
|
||||
2. Set `agents.defaults.workspace` to that path in `~/.bot/bot.json`.
|
||||
3. Run `bot setup --workspace <path>` to seed any missing files.
|
||||
4. If you need sessions, copy `~/.bot/agents/<agentId>/sessions/` from the
|
||||
old machine separately.
|
||||
|
||||
## Advanced notes
|
||||
|
||||
- Multi-agent routing can use different workspaces per agent. See
|
||||
[Channel routing](/concepts/channel-routing) for routing configuration.
|
||||
- If `agents.defaults.sandbox` is enabled, non-main sessions can use per-session sandbox
|
||||
workspaces under `agents.defaults.sandbox.workspaceRoot`.
|
||||
@@ -0,0 +1,115 @@
|
||||
---
|
||||
title: "Agent Runtime 🤖"
|
||||
description: "Agent runtime (embedded p-mono), workspace contract, and session bootstrap"
|
||||
---
|
||||
|
||||
Bot runs a single embedded agent runtime derived from **p-mono**.
|
||||
|
||||
## Workspace (required)
|
||||
|
||||
Bot uses a single agent workspace directory (`agents.defaults.workspace`) as the agent’s **only** working directory (`cwd`) for tools and context.
|
||||
|
||||
Recommended: use `bot setup` to create `~/.bot/bot.json` if missing and initialize the workspace files.
|
||||
|
||||
Full workspace layout + backup guide: [Agent workspace](/concepts/agent-workspace)
|
||||
|
||||
If `agents.defaults.sandbox` is enabled, non-main sessions can override this with
|
||||
per-session workspaces under `agents.defaults.sandbox.workspaceRoot` (see
|
||||
[Gateway configuration](/gateway/configuration)).
|
||||
|
||||
## Bootstrap files (injected)
|
||||
|
||||
Inside `agents.defaults.workspace`, Bot expects these user-editable files:
|
||||
- `AGENTS.md` — operating instructions + “memory”
|
||||
- `SOUL.md` — persona, boundaries, tone
|
||||
- `TOOLS.md` — user-maintained tool notes (e.g. `imsg`, `sag`, conventions)
|
||||
- `BOOTSTRAP.md` — one-time first-run ritual (deleted after completion)
|
||||
- `IDENTITY.md` — agent name/vibe/emoji
|
||||
- `USER.md` — user profile + preferred address
|
||||
|
||||
On the first turn of a new session, Bot injects the contents of these files directly into the agent context.
|
||||
|
||||
Blank files are skipped. Large files are trimmed and truncated with a marker so prompts stay lean (read the file for full content).
|
||||
|
||||
If a file is missing, Bot injects a single “missing file” marker line (and `bot setup` will create a safe default template).
|
||||
|
||||
`BOOTSTRAP.md` is only created for a **brand new workspace** (no other bootstrap files present). If you delete it after completing the ritual, it should not be recreated on later restarts.
|
||||
|
||||
To disable bootstrap file creation entirely (for pre-seeded workspaces), set:
|
||||
|
||||
```json5
|
||||
{ agent: { skipBootstrap: true } }
|
||||
```
|
||||
|
||||
## Built-in tools
|
||||
|
||||
Core tools (read/exec/edit/write and related system tools) are always available,
|
||||
subject to tool policy. `apply_patch` is optional and gated by
|
||||
`tools.exec.applyPatch`. `TOOLS.md` does **not** control which tools exist; it’s
|
||||
guidance for how *you* want them used.
|
||||
|
||||
## Skills
|
||||
|
||||
Bot loads skills from three locations (workspace wins on name conflict):
|
||||
- Bundled (shipped with the install)
|
||||
- Managed/local: `~/.bot/skills`
|
||||
- Workspace: `<workspace>/skills`
|
||||
|
||||
Skills can be gated by config/env (see `skills` in [Gateway configuration](/gateway/configuration)).
|
||||
|
||||
## p-mono integration
|
||||
|
||||
Bot reuses pieces of the p-mono codebase (models/tools), but **session management, discovery, and tool wiring are Bot-owned**.
|
||||
|
||||
- No p-coding agent runtime.
|
||||
- No `~/.pi/agent` or `<workspace>/.pi` settings are consulted.
|
||||
|
||||
## Sessions
|
||||
|
||||
Session transcripts are stored as JSONL at:
|
||||
- `~/.bot/agents/<agentId>/sessions/<SessionId>.jsonl`
|
||||
|
||||
The session ID is stable and chosen by Bot.
|
||||
Legacy Pi/Tau session folders are **not** read.
|
||||
|
||||
## Steering while streaming
|
||||
|
||||
When queue mode is `steer`, inbound messages are injected into the current run.
|
||||
The queue is checked **after each tool call**; if a queued message is present,
|
||||
remaining tool calls from the current assistant message are skipped (error tool
|
||||
results with "Skipped due to queued user message."), then the queued user
|
||||
message is injected before the next assistant response.
|
||||
|
||||
When queue mode is `followup` or `collect`, inbound messages are held until the
|
||||
current turn ends, then a new agent turn starts with the queued payloads. See
|
||||
[Queue](/concepts/queue) for mode + debounce/cap behavior.
|
||||
|
||||
Block streaming sends completed assistant blocks as soon as they finish; it is
|
||||
**off by default** (`agents.defaults.blockStreamingDefault: "off"`).
|
||||
Tune the boundary via `agents.defaults.blockStreamingBreak` (`text_end` vs `message_end`; defaults to text_end).
|
||||
Control soft block chunking with `agents.defaults.blockStreamingChunk` (defaults to
|
||||
800–1200 chars; prefers paragraph breaks, then newlines; sentences last).
|
||||
Coalesce streamed chunks with `agents.defaults.blockStreamingCoalesce` to reduce
|
||||
single-line spam (idle-based merging before send). Non-Telegram channels require
|
||||
explicit `*.blockStreaming: true` to enable block replies.
|
||||
Verbose tool summaries are emitted at tool start (no debounce); Control UI
|
||||
streams tool output via agent events when available.
|
||||
More details: [Streaming + chunking](/concepts/streaming).
|
||||
|
||||
## Model refs
|
||||
|
||||
Model refs in config (for example `agents.defaults.model` and `agents.defaults.models`) are parsed by splitting on the **first** `/`.
|
||||
|
||||
- Use `provider/model` when configuring models.
|
||||
- If the model ID itself contains `/` (OpenRouter-style), include the provider prefix (example: `openrouter/moonshotai/kimi-k2`).
|
||||
- If you omit the provider, Bot treats the input as an alias or a model for the **default provider** (only works when there is no `/` in the model ID).
|
||||
|
||||
## Configuration (minimal)
|
||||
|
||||
At minimum, set:
|
||||
- `agents.defaults.workspace`
|
||||
- `channels.whatsapp.allowFrom` (strongly recommended)
|
||||
|
||||
---
|
||||
|
||||
*Next: [Group Chats](/concepts/group-messages)* 🦞
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
title: "Gateway architecture"
|
||||
description: "WebSocket gateway architecture, components, and client flows"
|
||||
---
|
||||
|
||||
Last updated: 2026-01-22
|
||||
|
||||
## Overview
|
||||
|
||||
- A single long‑lived **Gateway** owns all messaging surfaces (WhatsApp via
|
||||
Baileys, Telegram via grammY, Slack, Discord, Signal, iMessage, WebChat).
|
||||
- Control-plane clients (macOS app, CLI, web UI, automations) connect to the
|
||||
Gateway over **WebSocket** on the configured bind host (default
|
||||
`127.0.0.1:18789`).
|
||||
- **Nodes** (macOS/iOS/Android/headless) also connect over **WebSocket**, but
|
||||
declare `role: node` with explicit caps/commands.
|
||||
- One Gateway per host; it is the only place that opens a WhatsApp session.
|
||||
- A **canvas host** (default `18793`) serves agent‑editable HTML and A2UI.
|
||||
|
||||
## Components and flows
|
||||
|
||||
### Gateway (daemon)
|
||||
- Maintains provider connections.
|
||||
- Exposes a typed WS API (requests, responses, server‑push events).
|
||||
- Validates inbound frames against JSON Schema.
|
||||
- Emits events like `agent`, `chat`, `presence`, `health`, `heartbeat`, `cron`.
|
||||
|
||||
### Clients (mac app / CLI / web admin)
|
||||
- One WS connection per client.
|
||||
- Send requests (`health`, `status`, `send`, `agent`, `system-presence`).
|
||||
- Subscribe to events (`tick`, `agent`, `presence`, `shutdown`).
|
||||
|
||||
### Nodes (macOS / iOS / Android / headless)
|
||||
- Connect to the **same WS server** with `role: node`.
|
||||
- Provide a device identity in `connect`; pairing is **device‑based** (role `node`) and
|
||||
approval lives in the device pairing store.
|
||||
- Expose commands like `canvas.*`, `camera.*`, `screen.record`, `location.get`.
|
||||
|
||||
Protocol details:
|
||||
- [Gateway protocol](/gateway/protocol)
|
||||
|
||||
### WebChat
|
||||
- Static UI that uses the Gateway WS API for chat history and sends.
|
||||
- In remote setups, connects through the same SSH/Tailscale tunnel as other
|
||||
clients.
|
||||
|
||||
## Connection lifecycle (single client)
|
||||
|
||||
```
|
||||
Client Gateway
|
||||
| |
|
||||
|---- req:connect -------->|
|
||||
|<------ res (ok) ---------| (or res error + close)
|
||||
| (payload=hello-ok carries snapshot: presence + health)
|
||||
| |
|
||||
|<------ event:presence ---|
|
||||
|<------ event:tick -------|
|
||||
| |
|
||||
|------- req:agent ------->|
|
||||
|<------ res:agent --------| (ack: {runId,status:"accepted"})
|
||||
|<------ event:agent ------| (streaming)
|
||||
|<------ res:agent --------| (final: {runId,status,summary})
|
||||
| |
|
||||
```
|
||||
|
||||
## Wire protocol (summary)
|
||||
|
||||
- Transport: WebSocket, text frames with JSON payloads.
|
||||
- First frame **must** be `connect`.
|
||||
- After handshake:
|
||||
- Requests: `{type:"req", id, method, params}` → `{type:"res", id, ok, payload|error}`
|
||||
- Events: `{type:"event", event, payload, seq?, stateVersion?}`
|
||||
- If `BOT_GATEWAY_TOKEN` (or `--token`) is set, `connect.params.auth.token`
|
||||
must match or the socket closes.
|
||||
- Idempotency keys are required for side‑effecting methods (`send`, `agent`) to
|
||||
safely retry; the server keeps a short‑lived dedupe cache.
|
||||
- Nodes must include `role: "node"` plus caps/commands/permissions in `connect`.
|
||||
|
||||
## Pairing + local trust
|
||||
|
||||
- All WS clients (operators + nodes) include a **device identity** on `connect`.
|
||||
- New device IDs require pairing approval; the Gateway issues a **device token**
|
||||
for subsequent connects.
|
||||
- **Local** connects (loopback or the gateway host’s own tailnet address) can be
|
||||
auto‑approved to keep same‑host UX smooth.
|
||||
- **Non‑local** connects must sign the `connect.challenge` nonce and require
|
||||
explicit approval.
|
||||
- Gateway auth (`gateway.auth.*`) still applies to **all** connections, local or
|
||||
remote.
|
||||
|
||||
Details: [Gateway protocol](/gateway/protocol), [Pairing](/start/pairing),
|
||||
[Security](/gateway/security).
|
||||
|
||||
## Protocol typing and codegen
|
||||
|
||||
- TypeBox schemas define the protocol.
|
||||
- JSON Schema is generated from those schemas.
|
||||
- Swift models are generated from the JSON Schema.
|
||||
|
||||
## Remote access
|
||||
|
||||
- Preferred: Tailscale or VPN.
|
||||
- Alternative: SSH tunnel
|
||||
```bash
|
||||
ssh -N -L 18789:127.0.0.1:18789 user@host
|
||||
```
|
||||
- The same handshake + auth token apply over the tunnel.
|
||||
- TLS + optional pinning can be enabled for WS in remote setups.
|
||||
|
||||
## Operations snapshot
|
||||
|
||||
- Start: `bot gateway` (foreground, logs to stdout).
|
||||
- Health: `health` over WS (also included in `hello-ok`).
|
||||
- Supervision: launchd/systemd for auto‑restart.
|
||||
|
||||
## Invariants
|
||||
|
||||
- Exactly one Gateway controls a single Baileys session per host.
|
||||
- Handshake is mandatory; any non‑JSON or non‑connect first frame is a hard close.
|
||||
- Events are not replayed; clients must refresh on gaps.
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
title: "Channels & routing"
|
||||
description: "Routing rules per channel (WhatsApp, Telegram, Discord, Slack) and shared context"
|
||||
---
|
||||
|
||||
Bot routes replies **back to the channel where a message came from**. The
|
||||
model does not choose a channel; routing is deterministic and controlled by the
|
||||
host configuration.
|
||||
|
||||
## Key terms
|
||||
|
||||
- **Channel**: `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `imessage`, `webchat`.
|
||||
- **AccountId**: per‑channel account instance (when supported).
|
||||
- **AgentId**: an isolated workspace + session store (“brain”).
|
||||
- **SessionKey**: the bucket key used to store context and control concurrency.
|
||||
|
||||
## Session key shapes (examples)
|
||||
|
||||
Direct messages collapse to the agent’s **main** session:
|
||||
|
||||
- `agent:<agentId>:<mainKey>` (default: `agent:main:main`)
|
||||
|
||||
Groups and channels remain isolated per channel:
|
||||
|
||||
- Groups: `agent:<agentId>:<channel>:group:<id>`
|
||||
- Channels/rooms: `agent:<agentId>:<channel>:channel:<id>`
|
||||
|
||||
Threads:
|
||||
|
||||
- Slack/Discord threads append `:thread:<threadId>` to the base key.
|
||||
- Telegram forum topics embed `:topic:<topicId>` in the group key.
|
||||
|
||||
Examples:
|
||||
|
||||
- `agent:main:telegram:group:-1001234567890:topic:42`
|
||||
- `agent:main:discord:channel:123456:thread:987654`
|
||||
|
||||
## Routing rules (how an agent is chosen)
|
||||
|
||||
Routing picks **one agent** for each inbound message:
|
||||
|
||||
1. **Exact peer match** (`bindings` with `peer.kind` + `peer.id`).
|
||||
2. **Guild match** (Discord) via `guildId`.
|
||||
3. **Team match** (Slack) via `teamId`.
|
||||
4. **Account match** (`accountId` on the channel).
|
||||
5. **Channel match** (any account on that channel).
|
||||
6. **Default agent** (`agents.list[].default`, else first list entry, fallback to `main`).
|
||||
|
||||
The matched agent determines which workspace and session store are used.
|
||||
|
||||
## Broadcast groups (run multiple agents)
|
||||
|
||||
Broadcast groups let you run **multiple agents** for the same peer **when Bot would normally reply** (for example: in WhatsApp groups, after mention/activation gating).
|
||||
|
||||
Config:
|
||||
|
||||
```json5
|
||||
{
|
||||
broadcast: {
|
||||
strategy: "parallel",
|
||||
"[email protected]": ["alfred", "baerbel"],
|
||||
"+15555550123": ["support", "logger"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See: [Broadcast Groups](/broadcast-groups).
|
||||
|
||||
## Config overview
|
||||
|
||||
- `agents.list`: named agent definitions (workspace, model, etc.).
|
||||
- `bindings`: map inbound channels/accounts/peers to agents.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "support", name: "Support", workspace: "~/bot-support" }
|
||||
]
|
||||
},
|
||||
bindings: [
|
||||
{ match: { channel: "slack", teamId: "T123" }, agentId: "support" },
|
||||
{ match: { channel: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Session storage
|
||||
|
||||
Session stores live under the state directory (default `~/.bot`):
|
||||
|
||||
- `~/.bot/agents/<agentId>/sessions/sessions.json`
|
||||
- JSONL transcripts live alongside the store
|
||||
|
||||
You can override the store path via `session.store` and `{agentId}` templating.
|
||||
|
||||
## WebChat behavior
|
||||
|
||||
WebChat attaches to the **selected agent** and defaults to the agent’s main
|
||||
session. Because of this, WebChat lets you see cross‑channel context for that
|
||||
agent in one place.
|
||||
|
||||
## Reply context
|
||||
|
||||
Inbound replies include:
|
||||
- `ReplyToId`, `ReplyToBody`, and `ReplyToSender` when available.
|
||||
- Quoted context is appended to `Body` as a `[Replying to ...]` block.
|
||||
|
||||
This is consistent across channels.
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: "Context Window & Compaction"
|
||||
description: "Context window + compaction: how Bot keeps sessions under model limits"
|
||||
---
|
||||
|
||||
Every model has a **context window** (max tokens it can see). Long-running chats accumulate messages and tool results; once the window is tight, Bot **compacts** older history to stay within limits.
|
||||
|
||||
## What compaction is
|
||||
Compaction **summarizes older conversation** into a compact summary entry and keeps recent messages intact. The summary is stored in the session history, so future requests use:
|
||||
- The compaction summary
|
||||
- Recent messages after the compaction point
|
||||
|
||||
Compaction **persists** in the session’s JSONL history.
|
||||
|
||||
## Configuration
|
||||
See [Compaction config & modes](/concepts/compaction) for the `agents.defaults.compaction` settings.
|
||||
|
||||
## Auto-compaction (default on)
|
||||
When a session nears or exceeds the model’s context window, Bot triggers auto-compaction and may retry the original request using the compacted context.
|
||||
|
||||
You’ll see:
|
||||
- `🧹 Auto-compaction complete` in verbose mode
|
||||
- `/status` showing `🧹 Compactions: <count>`
|
||||
|
||||
Before compaction, Bot can run a **silent memory flush** turn to store
|
||||
durable notes to disk. See [Memory](/concepts/memory) for details and config.
|
||||
|
||||
## Manual compaction
|
||||
Use `/compact` (optionally with instructions) to force a compaction pass:
|
||||
```
|
||||
/compact Focus on decisions and open questions
|
||||
```
|
||||
|
||||
## Context window source
|
||||
Context window is model-specific. Bot uses the model definition from the configured provider catalog to determine limits.
|
||||
|
||||
## Compaction vs pruning
|
||||
- **Compaction**: summarises and **persists** in JSONL.
|
||||
- **Session pruning**: trims old **tool results** only, **in-memory**, per request.
|
||||
|
||||
See [/concepts/session-pruning](/concepts/session-pruning) for pruning details.
|
||||
|
||||
## Tips
|
||||
- Use `/compact` when sessions feel stale or context is bloated.
|
||||
- Large tool outputs are already truncated; pruning can further reduce tool-result buildup.
|
||||
- If you need a fresh slate, `/new` or `/reset` starts a new session id.
|
||||
@@ -0,0 +1,147 @@
|
||||
---
|
||||
title: "Context"
|
||||
description: "Context: what the model sees, how it is built, and how to inspect it"
|
||||
---
|
||||
|
||||
“Context” is **everything Bot sends to the model for a run**. It is bounded by the model’s **context window** (token limit).
|
||||
|
||||
Beginner mental model:
|
||||
- **System prompt** (Bot-built): rules, tools, skills list, time/runtime, and injected workspace files.
|
||||
- **Conversation history**: your messages + the assistant’s messages for this session.
|
||||
- **Tool calls/results + attachments**: command output, file reads, images/audio, etc.
|
||||
|
||||
Context is *not the same thing* as “memory”: memory can be stored on disk and reloaded later; context is what’s inside the model’s current window.
|
||||
|
||||
## Quick start (inspect context)
|
||||
|
||||
- `/status` → quick “how full is my window?” view + session settings.
|
||||
- `/context list` → what’s injected + rough sizes (per file + totals).
|
||||
- `/context detail` → deeper breakdown: per-file, per-tool schema sizes, per-skill entry sizes, and system prompt size.
|
||||
- `/usage tokens` → append per-reply usage footer to normal replies.
|
||||
- `/compact` → summarize older history into a compact entry to free window space.
|
||||
|
||||
See also: [Slash commands](/tools/slash-commands), [Token use & costs](/token-use), [Compaction](/concepts/compaction).
|
||||
|
||||
## Example output
|
||||
|
||||
Values vary by model, provider, tool policy, and what’s in your workspace.
|
||||
|
||||
### `/context list`
|
||||
|
||||
```
|
||||
🧠 Context breakdown
|
||||
Workspace: <workspaceDir>
|
||||
Bootstrap max/file: 20,000 chars
|
||||
Sandbox: mode=non-main sandboxed=false
|
||||
System prompt (run): 38,412 chars (~9,603 tok) (Project Context 23,901 chars (~5,976 tok))
|
||||
|
||||
Injected workspace files:
|
||||
- AGENTS.md: OK | raw 1,742 chars (~436 tok) | injected 1,742 chars (~436 tok)
|
||||
- SOUL.md: OK | raw 912 chars (~228 tok) | injected 912 chars (~228 tok)
|
||||
- TOOLS.md: TRUNCATED | raw 54,210 chars (~13,553 tok) | injected 20,962 chars (~5,241 tok)
|
||||
- IDENTITY.md: OK | raw 211 chars (~53 tok) | injected 211 chars (~53 tok)
|
||||
- USER.md: OK | raw 388 chars (~97 tok) | injected 388 chars (~97 tok)
|
||||
- HEARTBEAT.md: MISSING | raw 0 | injected 0
|
||||
- BOOTSTRAP.md: OK | raw 0 chars (~0 tok) | injected 0 chars (~0 tok)
|
||||
|
||||
Skills list (system prompt text): 2,184 chars (~546 tok) (12 skills)
|
||||
Tools: read, edit, write, exec, process, browser, message, sessions_send, …
|
||||
Tool list (system prompt text): 1,032 chars (~258 tok)
|
||||
Tool schemas (JSON): 31,988 chars (~7,997 tok) (counts toward context; not shown as text)
|
||||
Tools: (same as above)
|
||||
|
||||
Session tokens (cached): 14,250 total / ctx=32,000
|
||||
```
|
||||
|
||||
### `/context detail`
|
||||
|
||||
```
|
||||
🧠 Context breakdown (detailed)
|
||||
…
|
||||
Top skills (prompt entry size):
|
||||
- frontend-design: 412 chars (~103 tok)
|
||||
- oracle: 401 chars (~101 tok)
|
||||
… (+10 more skills)
|
||||
|
||||
Top tools (schema size):
|
||||
- browser: 9,812 chars (~2,453 tok)
|
||||
- exec: 6,240 chars (~1,560 tok)
|
||||
… (+N more tools)
|
||||
```
|
||||
|
||||
## What counts toward the context window
|
||||
|
||||
Everything the model receives counts, including:
|
||||
- System prompt (all sections).
|
||||
- Conversation history.
|
||||
- Tool calls + tool results.
|
||||
- Attachments/transcripts (images/audio/files).
|
||||
- Compaction summaries and pruning artifacts.
|
||||
- Provider “wrappers” or hidden headers (not visible, still counted).
|
||||
|
||||
## How Bot builds the system prompt
|
||||
|
||||
The system prompt is **Bot-owned** and rebuilt each run. It includes:
|
||||
- Tool list + short descriptions.
|
||||
- Skills list (metadata only; see below).
|
||||
- Workspace location.
|
||||
- Time (UTC + converted user time if configured).
|
||||
- Runtime metadata (host/OS/model/thinking).
|
||||
- Injected workspace bootstrap files under **Project Context**.
|
||||
|
||||
Full breakdown: [System Prompt](/concepts/system-prompt).
|
||||
|
||||
## Injected workspace files (Project Context)
|
||||
|
||||
By default, Bot injects a fixed set of workspace files (if present):
|
||||
- `AGENTS.md`
|
||||
- `SOUL.md`
|
||||
- `TOOLS.md`
|
||||
- `IDENTITY.md`
|
||||
- `USER.md`
|
||||
- `HEARTBEAT.md`
|
||||
- `BOOTSTRAP.md` (first-run only)
|
||||
|
||||
Large files are truncated per-file using `agents.defaults.bootstrapMaxChars` (default `20000` chars). `/context` shows **raw vs injected** sizes and whether truncation happened.
|
||||
|
||||
## Skills: what’s injected vs loaded on-demand
|
||||
|
||||
The system prompt includes a compact **skills list** (name + description + location). This list has real overhead.
|
||||
|
||||
Skill instructions are *not* included by default. The model is expected to `read` the skill’s `SKILL.md` **only when needed**.
|
||||
|
||||
## Tools: there are two costs
|
||||
|
||||
Tools affect context in two ways:
|
||||
1) **Tool list text** in the system prompt (what you see as “Tooling”).
|
||||
2) **Tool schemas** (JSON). These are sent to the model so it can call tools. They count toward context even though you don’t see them as plain text.
|
||||
|
||||
`/context detail` breaks down the biggest tool schemas so you can see what dominates.
|
||||
|
||||
## Commands, directives, and “inline shortcuts”
|
||||
|
||||
Slash commands are handled by the Gateway. There are a few different behaviors:
|
||||
- **Standalone commands**: a message that is only `/...` runs as a command.
|
||||
- **Directives**: `/think`, `/verbose`, `/reasoning`, `/elevated`, `/model`, `/queue` are stripped before the model sees the message.
|
||||
- Directive-only messages persist session settings.
|
||||
- Inline directives in a normal message act as per-message hints.
|
||||
- **Inline shortcuts** (allowlisted senders only): certain `/...` tokens inside a normal message can run immediately (example: “hey /status”), and are stripped before the model sees the remaining text.
|
||||
|
||||
Details: [Slash commands](/tools/slash-commands).
|
||||
|
||||
## Sessions, compaction, and pruning (what persists)
|
||||
|
||||
What persists across messages depends on the mechanism:
|
||||
- **Normal history** persists in the session transcript until compacted/pruned by policy.
|
||||
- **Compaction** persists a summary into the transcript and keeps recent messages intact.
|
||||
- **Pruning** removes old tool results from the *in-memory* prompt for a run, but does not rewrite the transcript.
|
||||
|
||||
Docs: [Session](/concepts/session), [Compaction](/concepts/compaction), [Session pruning](/concepts/session-pruning).
|
||||
|
||||
## What `/context` actually reports
|
||||
|
||||
`/context` prefers the latest **run-built** system prompt report when available:
|
||||
- `System prompt (run)` = captured from the last embedded (tool-capable) run and persisted in the session store.
|
||||
- `System prompt (estimate)` = computed on the fly when no run report exists (or when running via a CLI backend that doesn’t generate the report).
|
||||
|
||||
Either way, it reports sizes and top contributors; it does **not** dump the full system prompt or tool schemas.
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: "Group messages (WhatsApp web channel)"
|
||||
description: "Behavior and config for WhatsApp group message handling (mentionPatterns are shared across surfaces)"
|
||||
---
|
||||
|
||||
Goal: let Bot sit in WhatsApp groups, wake up only when pinged, and keep that thread separate from the personal DM session.
|
||||
|
||||
Note: `agents.list[].groupChat.mentionPatterns` is now used by Telegram/Discord/Slack/iMessage as well; this doc focuses on WhatsApp-specific behavior. For multi-agent setups, set `agents.list[].groupChat.mentionPatterns` per agent (or use `messages.groupChat.mentionPatterns` as a global fallback).
|
||||
|
||||
## What’s implemented (2025-12-03)
|
||||
- Activation modes: `mention` (default) or `always`. `mention` requires a ping (real WhatsApp @-mentions via `mentionedJids`, regex patterns, or the bot’s E.164 anywhere in the text). `always` wakes the agent on every message but it should reply only when it can add meaningful value; otherwise it returns the silent token `NO_REPLY`. Defaults can be set in config (`channels.whatsapp.groups`) and overridden per group via `/activation`. When `channels.whatsapp.groups` is set, it also acts as a group allowlist (include `"*"` to allow all).
|
||||
- Group policy: `channels.whatsapp.groupPolicy` controls whether group messages are accepted (`open|disabled|allowlist`). `allowlist` uses `channels.whatsapp.groupAllowFrom` (fallback: explicit `channels.whatsapp.allowFrom`). Default is `allowlist` (blocked until you add senders).
|
||||
- Per-group sessions: session keys look like `agent:<agentId>:whatsapp:group:<jid>` so commands such as `/verbose on` or `/think high` (sent as standalone messages) are scoped to that group; personal DM state is untouched. Heartbeats are skipped for group threads.
|
||||
- Context injection: **pending-only** group messages (default 50) that *did not* trigger a run are prefixed under `[Chat messages since your last reply - for context]`, with the triggering line under `[Current message - respond to this]`. Messages already in the session are not re-injected.
|
||||
- Sender surfacing: every group batch now ends with `[from: Sender Name (+E164)]` so Pi knows who is speaking.
|
||||
- Ephemeral/view-once: we unwrap those before extracting text/mentions, so pings inside them still trigger.
|
||||
- Group system prompt: on the first turn of a group session (and whenever `/activation` changes the mode) we inject a short blurb into the system prompt like `You are replying inside the WhatsApp group "<subject>". Group members: Alice (+44...), Bob (+43...), … Activation: trigger-only … Address the specific sender noted in the message context.` If metadata isn’t available we still tell the agent it’s a group chat.
|
||||
|
||||
## Config example (WhatsApp)
|
||||
Add a `groupChat` block to `~/.bot/bot.json` so display-name pings work even when WhatsApp strips the visual `@` in the text body:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: {
|
||||
"*": { requireMention: true }
|
||||
}
|
||||
}
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
groupChat: {
|
||||
historyLimit: 50,
|
||||
mentionPatterns: [
|
||||
"@?bot",
|
||||
"\\+?15555550123"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- The regexes are case-insensitive; they cover a display-name ping like `@bot` and the raw number with or without `+`/spaces.
|
||||
- WhatsApp still sends canonical mentions via `mentionedJids` when someone taps the contact, so the number fallback is rarely needed but is a useful safety net.
|
||||
|
||||
### Activation command (owner-only)
|
||||
|
||||
Use the group chat command:
|
||||
- `/activation mention`
|
||||
- `/activation always`
|
||||
|
||||
Only the owner number (from `channels.whatsapp.allowFrom`, or the bot’s own E.164 when unset) can change this. Send `/status` as a standalone message in the group to see the current activation mode.
|
||||
|
||||
## How to use
|
||||
1) Add your WhatsApp account (the one running Bot) to the group.
|
||||
2) Say `@bot …` (or include the number). Only allowlisted senders can trigger it unless you set `groupPolicy: "open"`.
|
||||
3) The agent prompt will include recent group context plus the trailing `[from: …]` marker so it can address the right person.
|
||||
4) Session-level directives (`/verbose on`, `/think high`, `/new` or `/reset`, `/compact`) apply only to that group’s session; send them as standalone messages so they register. Your personal DM session remains independent.
|
||||
|
||||
## Testing / verification
|
||||
- Manual smoke:
|
||||
- Send an `@bot` ping in the group and confirm a reply that references the sender name.
|
||||
- Send a second ping and verify the history block is included then cleared on the next turn.
|
||||
- Check gateway logs (run with `--verbose`) to see `inbound web message` entries showing `from: <groupJid>` and the `[from: …]` suffix.
|
||||
|
||||
## Known considerations
|
||||
- Heartbeats are intentionally skipped for groups to avoid noisy broadcasts.
|
||||
- Echo suppression uses the combined batch string; if you send identical text twice without mentions, only the first will get a response.
|
||||
- Session store entries will appear as `agent:<agentId>:whatsapp:group:<jid>` in the session store (`~/.bot/agents/<agentId>/sessions/sessions.json` by default); a missing entry just means the group hasn’t triggered a run yet.
|
||||
- Typing indicators in groups follow `agents.defaults.typingMode` (default: `message` when unmentioned).
|
||||
@@ -0,0 +1,306 @@
|
||||
---
|
||||
title: "Groups"
|
||||
description: "Group chat behavior across surfaces (WhatsApp/Telegram/Discord/Slack/Signal/iMessage/Microsoft Teams)"
|
||||
---
|
||||
|
||||
Bot treats group chats consistently across surfaces: WhatsApp, Telegram, Discord, Slack, Signal, iMessage, Microsoft Teams.
|
||||
|
||||
## Beginner intro (2 minutes)
|
||||
Bot “lives” on your own messaging accounts. There is no separate WhatsApp bot user.
|
||||
If **you** are in a group, Bot can see that group and respond there.
|
||||
|
||||
Default behavior:
|
||||
- Groups are restricted (`groupPolicy: "allowlist"`).
|
||||
- Replies require a mention unless you explicitly disable mention gating.
|
||||
|
||||
Translation: allowlisted senders can trigger Bot by mentioning it.
|
||||
|
||||
> TL;DR
|
||||
> - **DM access** is controlled by `*.allowFrom`.
|
||||
> - **Group access** is controlled by `*.groupPolicy` + allowlists (`*.groups`, `*.groupAllowFrom`).
|
||||
> - **Reply triggering** is controlled by mention gating (`requireMention`, `/activation`).
|
||||
|
||||
Quick flow (what happens to a group message):
|
||||
```
|
||||
groupPolicy? disabled -> drop
|
||||
groupPolicy? allowlist -> group allowed? no -> drop
|
||||
requireMention? yes -> mentioned? no -> store for context only
|
||||
otherwise -> reply
|
||||
```
|
||||
|
||||
{/* Image: Group message flow diagram */}
|
||||
|
||||
If you want...
|
||||
| Goal | What to set |
|
||||
|------|-------------|
|
||||
| Allow all groups but only reply on @mentions | `groups: { "*": { requireMention: true } }` |
|
||||
| Disable all group replies | `groupPolicy: "disabled"` |
|
||||
| Only specific groups | `groups: { "<group-id>": { ... } }` (no `"*"` key) |
|
||||
| Only you can trigger in groups | `groupPolicy: "allowlist"`, `groupAllowFrom: ["+1555..."]` |
|
||||
|
||||
## Session keys
|
||||
- Group sessions use `agent:<agentId>:<channel>:group:<id>` session keys (rooms/channels use `agent:<agentId>:<channel>:channel:<id>`).
|
||||
- Telegram forum topics add `:topic:<threadId>` to the group id so each topic has its own session.
|
||||
- Direct chats use the main session (or per-sender if configured).
|
||||
- Heartbeats are skipped for group sessions.
|
||||
|
||||
## Pattern: personal DMs + public groups (single agent)
|
||||
|
||||
Yes — this works well if your “personal” traffic is **DMs** and your “public” traffic is **groups**.
|
||||
|
||||
Why: in single-agent mode, DMs typically land in the **main** session key (`agent:main:main`), while groups always use **non-main** session keys (`agent:main:<channel>:group:<id>`). If you enable sandboxing with `mode: "non-main"`, those group sessions run in Docker while your main DM session stays on-host.
|
||||
|
||||
This gives you one agent “brain” (shared workspace + memory), but two execution postures:
|
||||
- **DMs**: full tools (host)
|
||||
- **Groups**: sandbox + restricted tools (Docker)
|
||||
|
||||
> If you need truly separate workspaces/personas (“personal” and “public” must never mix), use a second agent + bindings. See [Multi-Agent Routing](/concepts/multi-agent).
|
||||
|
||||
Example (DMs on host, groups sandboxed + messaging-only tools):
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
mode: "non-main", // groups/channels are non-main -> sandboxed
|
||||
scope: "session", // strongest isolation (one container per group/channel)
|
||||
workspaceAccess: "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
tools: {
|
||||
sandbox: {
|
||||
tools: {
|
||||
// If allow is non-empty, everything else is blocked (deny still wins).
|
||||
allow: ["group:messaging", "group:sessions"],
|
||||
deny: ["group:runtime", "group:fs", "group:ui", "nodes", "cron", "gateway"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Want “groups can only see folder X” instead of “no host access”? Keep `workspaceAccess: "none"` and mount only allowlisted paths into the sandbox:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
mode: "non-main",
|
||||
scope: "session",
|
||||
workspaceAccess: "none",
|
||||
docker: {
|
||||
binds: [
|
||||
// hostPath:containerPath:mode
|
||||
"~/FriendsShared:/data:ro"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Related:
|
||||
- Configuration keys and defaults: [Gateway configuration](/gateway/configuration#agentsdefaultssandbox)
|
||||
- Debugging why a tool is blocked: [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated)
|
||||
- Bind mounts details: [Sandboxing](/gateway/sandboxing#custom-bind-mounts)
|
||||
|
||||
## Display labels
|
||||
- UI labels use `displayName` when available, formatted as `<channel>:<token>`.
|
||||
- `#room` is reserved for rooms/channels; group chats use `g-<slug>` (lowercase, spaces -> `-`, keep `#@+._-`).
|
||||
|
||||
## Group policy
|
||||
Control how group/room messages are handled per channel:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groupPolicy: "disabled", // "open" | "disabled" | "allowlist"
|
||||
groupAllowFrom: ["+15551234567"]
|
||||
},
|
||||
telegram: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["123456789", "@username"]
|
||||
},
|
||||
signal: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["+15551234567"]
|
||||
},
|
||||
imessage: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["chat_id:123"]
|
||||
},
|
||||
msteams: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["[email protected]"]
|
||||
},
|
||||
discord: {
|
||||
groupPolicy: "allowlist",
|
||||
guilds: {
|
||||
"GUILD_ID": { channels: { help: { allow: true } } }
|
||||
}
|
||||
},
|
||||
slack: {
|
||||
groupPolicy: "allowlist",
|
||||
channels: { "#general": { allow: true } }
|
||||
},
|
||||
matrix: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["@owner:example.org"],
|
||||
groups: {
|
||||
"!roomId:example.org": { allow: true },
|
||||
"#alias:example.org": { allow: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Policy | Behavior |
|
||||
|--------|----------|
|
||||
| `"open"` | Groups bypass allowlists; mention-gating still applies. |
|
||||
| `"disabled"` | Block all group messages entirely. |
|
||||
| `"allowlist"` | Only allow groups/rooms that match the configured allowlist. |
|
||||
|
||||
Notes:
|
||||
- `groupPolicy` is separate from mention-gating (which requires @mentions).
|
||||
- WhatsApp/Telegram/Signal/iMessage/Microsoft Teams: use `groupAllowFrom` (fallback: explicit `allowFrom`).
|
||||
- Discord: allowlist uses `channels.discord.guilds.<id>.channels`.
|
||||
- Slack: allowlist uses `channels.slack.channels`.
|
||||
- Matrix: allowlist uses `channels.matrix.groups` (room IDs, aliases, or names). Use `channels.matrix.groupAllowFrom` to restrict senders; per-room `users` allowlists are also supported.
|
||||
- Group DMs are controlled separately (`channels.discord.dm.*`, `channels.slack.dm.*`).
|
||||
- Telegram allowlist can match user IDs (`"123456789"`, `"telegram:123456789"`, `"tg:123456789"`) or usernames (`"@alice"` or `"alice"`); prefixes are case-insensitive.
|
||||
- Default is `groupPolicy: "allowlist"`; if your group allowlist is empty, group messages are blocked.
|
||||
|
||||
Quick mental model (evaluation order for group messages):
|
||||
1) `groupPolicy` (open/disabled/allowlist)
|
||||
2) group allowlists (`*.groups`, `*.groupAllowFrom`, channel-specific allowlist)
|
||||
3) mention gating (`requireMention`, `/activation`)
|
||||
|
||||
## Mention gating (default)
|
||||
Group messages require a mention unless overridden per group. Defaults live per subsystem under `*.groups."*"`.
|
||||
|
||||
Replying to a bot message counts as an implicit mention (when the channel supports reply metadata). This applies to Telegram, WhatsApp, Slack, Discord, and Microsoft Teams.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
"[email protected]": { requireMention: false }
|
||||
}
|
||||
},
|
||||
telegram: {
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
"123456789": { requireMention: false }
|
||||
}
|
||||
},
|
||||
imessage: {
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
"123": { requireMention: false }
|
||||
}
|
||||
}
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
groupChat: {
|
||||
mentionPatterns: ["@bot", "bot", "\\+15555550123"],
|
||||
historyLimit: 50
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `mentionPatterns` are case-insensitive regexes.
|
||||
- Surfaces that provide explicit mentions still pass; patterns are a fallback.
|
||||
- Per-agent override: `agents.list[].groupChat.mentionPatterns` (useful when multiple agents share a group).
|
||||
- Mention gating is only enforced when mention detection is possible (native mentions or `mentionPatterns` are configured).
|
||||
- Discord defaults live in `channels.discord.guilds."*"` (overridable per guild/channel).
|
||||
- Group history context is wrapped uniformly across channels and is **pending-only** (messages skipped due to mention gating); use `messages.groupChat.historyLimit` for the global default and `channels.<channel>.historyLimit` (or `channels.<channel>.accounts.*.historyLimit`) for overrides. Set `0` to disable.
|
||||
|
||||
## Group allowlists
|
||||
When `channels.whatsapp.groups`, `channels.telegram.groups`, or `channels.imessage.groups` is configured, the keys act as a group allowlist. Use `"*"` to allow all groups while still setting default mention behavior.
|
||||
|
||||
Common intents (copy/paste):
|
||||
|
||||
1) Disable all group replies
|
||||
```json5
|
||||
{
|
||||
channels: { whatsapp: { groupPolicy: "disabled" } }
|
||||
}
|
||||
```
|
||||
|
||||
2) Allow only specific groups (WhatsApp)
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: {
|
||||
"[email protected]": { requireMention: true },
|
||||
"[email protected]": { requireMention: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3) Allow all groups but require mention (explicit)
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: { "*": { requireMention: true } }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4) Only the owner can trigger in groups (WhatsApp)
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15551234567"],
|
||||
groups: { "*": { requireMention: true } }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Activation (owner-only)
|
||||
Group owners can toggle per-group activation:
|
||||
- `/activation mention`
|
||||
- `/activation always`
|
||||
|
||||
Owner is determined by `channels.whatsapp.allowFrom` (or the bot’s self E.164 when unset). Send the command as a standalone message. Other surfaces currently ignore `/activation`.
|
||||
|
||||
## Context fields
|
||||
Group inbound payloads set:
|
||||
- `ChatType=group`
|
||||
- `GroupSubject` (if known)
|
||||
- `GroupMembers` (if known)
|
||||
- `WasMentioned` (mention gating result)
|
||||
- Telegram forum topics also include `MessageThreadId` and `IsForum`.
|
||||
|
||||
The agent system prompt includes a group intro on the first turn of a new group session. It reminds the model to respond like a human, avoid Markdown tables, and avoid typing literal `\n` sequences.
|
||||
|
||||
## iMessage specifics
|
||||
- Prefer `chat_id:<id>` when routing or allowlisting.
|
||||
- List chats: `imsg chats --limit 20`.
|
||||
- Group replies always go back to the same `chat_id`.
|
||||
|
||||
## WhatsApp specifics
|
||||
See [Group messages](/concepts/group-messages) for WhatsApp-only behavior (history injection, mention handling details).
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
title: "Markdown formatting"
|
||||
description: "Markdown formatting pipeline for outbound channels"
|
||||
---
|
||||
|
||||
Bot formats outbound Markdown by converting it into a shared intermediate
|
||||
representation (IR) before rendering channel-specific output. The IR keeps the
|
||||
source text intact while carrying style/link spans so chunking and rendering can
|
||||
stay consistent across channels.
|
||||
|
||||
## Goals
|
||||
|
||||
- **Consistency:** one parse step, multiple renderers.
|
||||
- **Safe chunking:** split text before rendering so inline formatting never
|
||||
breaks across chunks.
|
||||
- **Channel fit:** map the same IR to Slack mrkdwn, Telegram HTML, and Signal
|
||||
style ranges without re-parsing Markdown.
|
||||
|
||||
## Pipeline
|
||||
|
||||
1. **Parse Markdown -> IR**
|
||||
- IR is plain text plus style spans (bold/italic/strike/code/spoiler) and link spans.
|
||||
- Offsets are UTF-16 code units so Signal style ranges align with its API.
|
||||
- Tables are parsed only when a channel opts into table conversion.
|
||||
2. **Chunk IR (format-first)**
|
||||
- Chunking happens on the IR text before rendering.
|
||||
- Inline formatting does not split across chunks; spans are sliced per chunk.
|
||||
3. **Render per channel**
|
||||
- **Slack:** mrkdwn tokens (bold/italic/strike/code), links as `<url|label>`.
|
||||
- **Telegram:** HTML tags (`<b>`, `<i>`, `<s>`, `<code>`, `<pre><code>`, `<a href>`).
|
||||
- **Signal:** plain text + `text-style` ranges; links become `label (url)` when label differs.
|
||||
|
||||
## IR example
|
||||
|
||||
Input Markdown:
|
||||
|
||||
```markdown
|
||||
Hello **world** — see [docs](https://docs.hanzo.bot).
|
||||
```
|
||||
|
||||
IR (schematic):
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "Hello world — see docs.",
|
||||
"styles": [
|
||||
{ "start": 6, "end": 11, "style": "bold" }
|
||||
],
|
||||
"links": [
|
||||
{ "start": 19, "end": 23, "href": "https://docs.hanzo.bot" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Where it is used
|
||||
|
||||
- Slack, Telegram, and Signal outbound adapters render from the IR.
|
||||
- Other channels (WhatsApp, iMessage, MS Teams, Discord) still use plain text or
|
||||
their own formatting rules, with Markdown table conversion applied before
|
||||
chunking when enabled.
|
||||
|
||||
## Table handling
|
||||
|
||||
Markdown tables are not consistently supported across chat clients. Use
|
||||
`markdown.tables` to control conversion per channel (and per account).
|
||||
|
||||
- `code`: render tables as code blocks (default for most channels).
|
||||
- `bullets`: convert each row into bullet points (default for Signal + WhatsApp).
|
||||
- `off`: disable table parsing and conversion; raw table text passes through.
|
||||
|
||||
Config keys:
|
||||
|
||||
```yaml
|
||||
channels:
|
||||
discord:
|
||||
markdown:
|
||||
tables: code
|
||||
accounts:
|
||||
work:
|
||||
markdown:
|
||||
tables: off
|
||||
```
|
||||
|
||||
## Chunking rules
|
||||
|
||||
- Chunk limits come from channel adapters/config and are applied to the IR text.
|
||||
- Code fences are preserved as a single block with a trailing newline so channels
|
||||
render them correctly.
|
||||
- List prefixes and blockquote prefixes are part of the IR text, so chunking
|
||||
does not split mid-prefix.
|
||||
- Inline styles (bold/italic/strike/inline-code/spoiler) are never split across
|
||||
chunks; the renderer reopens styles inside each chunk.
|
||||
|
||||
If you need more on chunking behavior across channels, see
|
||||
[Streaming + chunking](/concepts/streaming).
|
||||
|
||||
## Link policy
|
||||
|
||||
- **Slack:** `[label](url)` -> `<url|label>`; bare URLs remain bare. Autolink
|
||||
is disabled during parse to avoid double-linking.
|
||||
- **Telegram:** `[label](url)` -> `<a href="url">label</a>` (HTML parse mode).
|
||||
- **Signal:** `[label](url)` -> `label (url)` unless label matches the URL.
|
||||
|
||||
## Spoilers
|
||||
|
||||
Spoiler markers (`||spoiler||`) are parsed only for Signal, where they map to
|
||||
SPOILER style ranges. Other channels treat them as plain text.
|
||||
|
||||
## How to add or update a channel formatter
|
||||
|
||||
1. **Parse once:** use the shared `markdownToIR(...)` helper with channel-appropriate
|
||||
options (autolink, heading style, blockquote prefix).
|
||||
2. **Render:** implement a renderer with `renderMarkdownWithMarkers(...)` and a
|
||||
style marker map (or Signal style ranges).
|
||||
3. **Chunk:** call `chunkMarkdownIR(...)` before rendering; render each chunk.
|
||||
4. **Wire adapter:** update the channel outbound adapter to use the new chunker
|
||||
and renderer.
|
||||
5. **Test:** add or update format tests and an outbound delivery test if the
|
||||
channel uses chunking.
|
||||
|
||||
## Common gotchas
|
||||
|
||||
- Slack angle-bracket tokens (`<@U123>`, `<#C123>`, `<https://...>`) must be
|
||||
preserved; escape raw HTML safely.
|
||||
- Telegram HTML requires escaping text outside tags to avoid broken markup.
|
||||
- Signal style ranges depend on UTF-16 offsets; do not use code point offsets.
|
||||
- Preserve trailing newlines for fenced code blocks so closing markers land on
|
||||
their own line.
|
||||
@@ -0,0 +1,385 @@
|
||||
---
|
||||
title: "Memory"
|
||||
description: "How Bot memory works (workspace files + automatic memory flush)"
|
||||
---
|
||||
|
||||
Bot memory is **plain Markdown in the agent workspace**. The files are the
|
||||
source of truth; the model only "remembers" what gets written to disk.
|
||||
|
||||
Memory search tools are provided by the active memory plugin (default:
|
||||
`memory-core`). Disable memory plugins with `plugins.slots.memory = "none"`.
|
||||
|
||||
## Memory files (Markdown)
|
||||
|
||||
The default workspace layout uses two memory layers:
|
||||
|
||||
- `memory/YYYY-MM-DD.md`
|
||||
- Daily log (append-only).
|
||||
- Read today + yesterday at session start.
|
||||
- `MEMORY.md` (optional)
|
||||
- Curated long-term memory.
|
||||
- **Only load in the main, private session** (never in group contexts).
|
||||
|
||||
These files live under the workspace (`agents.defaults.workspace`, default
|
||||
`~/bot`). See [Agent workspace](/concepts/agent-workspace) for the full layout.
|
||||
|
||||
## When to write memory
|
||||
|
||||
- Decisions, preferences, and durable facts go to `MEMORY.md`.
|
||||
- Day-to-day notes and running context go to `memory/YYYY-MM-DD.md`.
|
||||
- If someone says "remember this," write it down (do not keep it in RAM).
|
||||
- This area is still evolving. It helps to remind the model to store memories; it will know what to do.
|
||||
- If you want something to stick, **ask the bot to write it** into memory.
|
||||
|
||||
## Automatic memory flush (pre-compaction ping)
|
||||
|
||||
When a session is **close to auto-compaction**, Bot triggers a **silent,
|
||||
agentic turn** that reminds the model to write durable memory **before** the
|
||||
context is compacted. The default prompts explicitly say the model *may reply*,
|
||||
but usually `NO_REPLY` is the correct response so the user never sees this turn.
|
||||
|
||||
This is controlled by `agents.defaults.compaction.memoryFlush`:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
compaction: {
|
||||
reserveTokensFloor: 20000,
|
||||
memoryFlush: {
|
||||
enabled: true,
|
||||
softThresholdTokens: 4000,
|
||||
systemPrompt: "Session nearing compaction. Store durable memories now.",
|
||||
prompt: "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Details:
|
||||
- **Soft threshold**: flush triggers when the session token estimate crosses
|
||||
`contextWindow - reserveTokensFloor - softThresholdTokens`.
|
||||
- **Silent** by default: prompts include `NO_REPLY` so nothing is delivered.
|
||||
- **Two prompts**: a user prompt plus a system prompt append the reminder.
|
||||
- **One flush per compaction cycle** (tracked in `sessions.json`).
|
||||
- **Workspace must be writable**: if the session runs sandboxed with
|
||||
`workspaceAccess: "ro"` or `"none"`, the flush is skipped.
|
||||
|
||||
For the full compaction lifecycle, see
|
||||
[Session management + compaction](/reference/session-management-compaction).
|
||||
|
||||
## Vector memory search
|
||||
|
||||
Bot can build a small vector index over `MEMORY.md` and `memory/*.md` so
|
||||
semantic queries can find related notes even when wording differs.
|
||||
|
||||
Defaults:
|
||||
- Enabled by default.
|
||||
- Watches memory files for changes (debounced).
|
||||
- Uses remote embeddings by default. If `memorySearch.provider` is not set, Bot auto-selects:
|
||||
1. `local` if a `memorySearch.local.modelPath` is configured and the file exists.
|
||||
2. `openai` if an OpenAI key can be resolved.
|
||||
3. `gemini` if a Gemini key can be resolved.
|
||||
4. Otherwise memory search stays disabled until configured.
|
||||
- Local mode uses node-llama-cpp and may require `pnpm approve-builds`.
|
||||
- Uses sqlite-vec (when available) to accelerate vector search inside SQLite.
|
||||
|
||||
Remote embeddings **require** an API key for the embedding provider. Bot
|
||||
resolves keys from auth profiles, `models.providers.*.apiKey`, or environment
|
||||
variables. Codex OAuth only covers chat/completions and does **not** satisfy
|
||||
embeddings for memory search. For Gemini, use `GEMINI_API_KEY` or
|
||||
`models.providers.google.apiKey`. When using a custom OpenAI-compatible endpoint,
|
||||
set `memorySearch.remote.apiKey` (and optional `memorySearch.remote.headers`).
|
||||
|
||||
### Gemini embeddings (native)
|
||||
|
||||
Set the provider to `gemini` to use the Gemini embeddings API directly:
|
||||
|
||||
```json5
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "gemini",
|
||||
model: "gemini-embedding-001",
|
||||
remote: {
|
||||
apiKey: "YOUR_GEMINI_API_KEY"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `remote.baseUrl` is optional (defaults to the Gemini API base URL).
|
||||
- `remote.headers` lets you add extra headers if needed.
|
||||
- Default model: `gemini-embedding-001`.
|
||||
|
||||
If you want to use a **custom OpenAI-compatible endpoint** (OpenRouter, vLLM, or a proxy),
|
||||
you can use the `remote` configuration with the OpenAI provider:
|
||||
|
||||
```json5
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "openai",
|
||||
model: "text-embedding-3-small",
|
||||
remote: {
|
||||
baseUrl: "https://api.example.com/v1/",
|
||||
apiKey: "YOUR_OPENAI_COMPAT_API_KEY",
|
||||
headers: { "X-Custom-Header": "value" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you don't want to set an API key, use `memorySearch.provider = "local"` or set
|
||||
`memorySearch.fallback = "none"`.
|
||||
|
||||
Fallbacks:
|
||||
- `memorySearch.fallback` can be `openai`, `gemini`, `local`, or `none`.
|
||||
- The fallback provider is only used when the primary embedding provider fails.
|
||||
|
||||
Batch indexing (OpenAI + Gemini):
|
||||
- Enabled by default for OpenAI and Gemini embeddings. Set `agents.defaults.memorySearch.remote.batch.enabled = false` to disable.
|
||||
- Default behavior waits for batch completion; tune `remote.batch.wait`, `remote.batch.pollIntervalMs`, and `remote.batch.timeoutMinutes` if needed.
|
||||
- Set `remote.batch.concurrency` to control how many batch jobs we submit in parallel (default: 2).
|
||||
- Batch mode applies when `memorySearch.provider = "openai"` or `"gemini"` and uses the corresponding API key.
|
||||
- Gemini batch jobs use the async embeddings batch endpoint and require Gemini Batch API availability.
|
||||
|
||||
Why OpenAI batch is fast + cheap:
|
||||
- For large backfills, OpenAI is typically the fastest option we support because we can submit many embedding requests in a single batch job and let OpenAI process them asynchronously.
|
||||
- OpenAI offers discounted pricing for Batch API workloads, so large indexing runs are usually cheaper than sending the same requests synchronously.
|
||||
- See the OpenAI Batch API docs and pricing for details:
|
||||
- https://platform.openai.com/docs/api-reference/batch
|
||||
- https://platform.openai.com/pricing
|
||||
|
||||
Config example:
|
||||
|
||||
```json5
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "openai",
|
||||
model: "text-embedding-3-small",
|
||||
fallback: "openai",
|
||||
remote: {
|
||||
batch: { enabled: true, concurrency: 2 }
|
||||
},
|
||||
sync: { watch: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Tools:
|
||||
- `memory_search` — returns snippets with file + line ranges.
|
||||
- `memory_get` — read memory file content by path.
|
||||
|
||||
Local mode:
|
||||
- Set `agents.defaults.memorySearch.provider = "local"`.
|
||||
- Provide `agents.defaults.memorySearch.local.modelPath` (GGUF or `hf:` URI).
|
||||
- Optional: set `agents.defaults.memorySearch.fallback = "none"` to avoid remote fallback.
|
||||
|
||||
### How the memory tools work
|
||||
|
||||
- `memory_search` semantically searches Markdown chunks (~400 token target, 80-token overlap) from `MEMORY.md` + `memory/**/*.md`. It returns snippet text (capped ~700 chars), file path, line range, score, provider/model, and whether we fell back from local → remote embeddings. No full file payload is returned.
|
||||
- `memory_get` reads a specific memory Markdown file (workspace-relative), optionally from a starting line and for N lines. Paths outside `MEMORY.md` / `memory/` are rejected.
|
||||
- Both tools are enabled only when `memorySearch.enabled` resolves true for the agent.
|
||||
|
||||
### What gets indexed (and when)
|
||||
|
||||
- File type: Markdown only (`MEMORY.md`, `memory/**/*.md`).
|
||||
- Index storage: per-agent SQLite at `~/.bot/memory/<agentId>.sqlite` (configurable via `agents.defaults.memorySearch.store.path`, supports `{agentId}` token).
|
||||
- Freshness: watcher on `MEMORY.md` + `memory/` marks the index dirty (debounce 1.5s). Sync is scheduled on session start, on search, or on an interval and runs asynchronously. Session transcripts use delta thresholds to trigger background sync.
|
||||
- Reindex triggers: the index stores the embedding **provider/model + endpoint fingerprint + chunking params**. If any of those change, Bot automatically resets and reindexes the entire store.
|
||||
|
||||
### Hybrid search (BM25 + vector)
|
||||
|
||||
When enabled, Bot combines:
|
||||
- **Vector similarity** (semantic match, wording can differ)
|
||||
- **BM25 keyword relevance** (exact tokens like IDs, env vars, code symbols)
|
||||
|
||||
If full-text search is unavailable on your platform, Bot falls back to vector-only search.
|
||||
|
||||
#### Why hybrid?
|
||||
|
||||
Vector search is great at “this means the same thing”:
|
||||
- “Mac Studio gateway host” vs “the machine running the gateway”
|
||||
- “debounce file updates” vs “avoid indexing on every write”
|
||||
|
||||
But it can be weak at exact, high-signal tokens:
|
||||
- IDs (`a828e60`, `b3b9895a…`)
|
||||
- code symbols (`memorySearch.query.hybrid`)
|
||||
- error strings (“sqlite-vec unavailable”)
|
||||
|
||||
BM25 (full-text) is the opposite: strong at exact tokens, weaker at paraphrases.
|
||||
Hybrid search is the pragmatic middle ground: **use both retrieval signals** so you get
|
||||
good results for both “natural language” queries and “needle in a haystack” queries.
|
||||
|
||||
#### How we merge results (the current design)
|
||||
|
||||
Implementation sketch:
|
||||
|
||||
1) Retrieve a candidate pool from both sides:
|
||||
- **Vector**: top `maxResults * candidateMultiplier` by cosine similarity.
|
||||
- **BM25**: top `maxResults * candidateMultiplier` by FTS5 BM25 rank (lower is better).
|
||||
|
||||
2) Convert BM25 rank into a 0..1-ish score:
|
||||
- `textScore = 1 / (1 + max(0, bm25Rank))`
|
||||
|
||||
3) Union candidates by chunk id and compute a weighted score:
|
||||
- `finalScore = vectorWeight * vectorScore + textWeight * textScore`
|
||||
|
||||
Notes:
|
||||
- `vectorWeight` + `textWeight` is normalized to 1.0 in config resolution, so weights behave as percentages.
|
||||
- If embeddings are unavailable (or the provider returns a zero-vector), we still run BM25 and return keyword matches.
|
||||
- If FTS5 can’t be created, we keep vector-only search (no hard failure).
|
||||
|
||||
This isn’t “IR-theory perfect”, but it’s simple, fast, and tends to improve recall/precision on real notes.
|
||||
If we want to get fancier later, common next steps are Reciprocal Rank Fusion (RRF) or score normalization
|
||||
(min/max or z-score) before mixing.
|
||||
|
||||
Config:
|
||||
|
||||
```json5
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
query: {
|
||||
hybrid: {
|
||||
enabled: true,
|
||||
vectorWeight: 0.7,
|
||||
textWeight: 0.3,
|
||||
candidateMultiplier: 4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Embedding cache
|
||||
|
||||
Bot can cache **chunk embeddings** in SQLite so reindexing and frequent updates (especially session transcripts) don't re-embed unchanged text.
|
||||
|
||||
Config:
|
||||
|
||||
```json5
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
cache: {
|
||||
enabled: true,
|
||||
maxEntries: 50000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Session memory search (experimental)
|
||||
|
||||
You can optionally index **session transcripts** and surface them via `memory_search`.
|
||||
This is gated behind an experimental flag.
|
||||
|
||||
```json5
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
experimental: { sessionMemory: true },
|
||||
sources: ["memory", "sessions"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Session indexing is **opt-in** (off by default).
|
||||
- Session updates are debounced and **indexed asynchronously** once they cross delta thresholds (best-effort).
|
||||
- `memory_search` never blocks on indexing; results can be slightly stale until background sync finishes.
|
||||
- Results still include snippets only; `memory_get` remains limited to memory files.
|
||||
- Session indexing is isolated per agent (only that agent’s session logs are indexed).
|
||||
- Session logs live on disk (`~/.bot/agents/<agentId>/sessions/*.jsonl`). Any process/user with filesystem access can read them, so treat disk access as the trust boundary. For stricter isolation, run agents under separate OS users or hosts.
|
||||
|
||||
Delta thresholds (defaults shown):
|
||||
|
||||
```json5
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
sync: {
|
||||
sessions: {
|
||||
deltaBytes: 100000, // ~100 KB
|
||||
deltaMessages: 50 // JSONL lines
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### SQLite vector acceleration (sqlite-vec)
|
||||
|
||||
When the sqlite-vec extension is available, Bot stores embeddings in a
|
||||
SQLite virtual table (`vec0`) and performs vector distance queries in the
|
||||
database. This keeps search fast without loading every embedding into JS.
|
||||
|
||||
Configuration (optional):
|
||||
|
||||
```json5
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
store: {
|
||||
vector: {
|
||||
enabled: true,
|
||||
extensionPath: "/path/to/sqlite-vec"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `enabled` defaults to true; when disabled, search falls back to in-process
|
||||
cosine similarity over stored embeddings.
|
||||
- If the sqlite-vec extension is missing or fails to load, Bot logs the
|
||||
error and continues with the JS fallback (no vector table).
|
||||
- `extensionPath` overrides the bundled sqlite-vec path (useful for custom builds
|
||||
or non-standard install locations).
|
||||
|
||||
### Local embedding auto-download
|
||||
|
||||
- Default local embedding model: `hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf` (~0.6 GB).
|
||||
- When `memorySearch.provider = "local"`, `node-llama-cpp` resolves `modelPath`; if the GGUF is missing it **auto-downloads** to the cache (or `local.modelCacheDir` if set), then loads it. Downloads resume on retry.
|
||||
- Native build requirement: run `pnpm approve-builds`, pick `node-llama-cpp`, then `pnpm rebuild node-llama-cpp`.
|
||||
- Fallback: if local setup fails and `memorySearch.fallback = "openai"`, we automatically switch to remote embeddings (`openai/text-embedding-3-small` unless overridden) and record the reason.
|
||||
|
||||
### Custom OpenAI-compatible endpoint example
|
||||
|
||||
```json5
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "openai",
|
||||
model: "text-embedding-3-small",
|
||||
remote: {
|
||||
baseUrl: "https://api.example.com/v1/",
|
||||
apiKey: "YOUR_REMOTE_API_KEY",
|
||||
headers: {
|
||||
"X-Organization": "org-id",
|
||||
"X-Project": "project-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `remote.*` takes precedence over `models.providers.openai.*`.
|
||||
- `remote.headers` merge with OpenAI headers; remote wins on key conflicts. Omit `remote.headers` to use the OpenAI defaults.
|
||||
@@ -0,0 +1,139 @@
|
||||
---
|
||||
title: "Messages"
|
||||
description: "Message flow, sessions, queueing, and reasoning visibility"
|
||||
---
|
||||
|
||||
This page ties together how Bot handles inbound messages, sessions, queueing,
|
||||
streaming, and reasoning visibility.
|
||||
|
||||
## Message flow (high level)
|
||||
|
||||
```
|
||||
Inbound message
|
||||
-> routing/bindings -> session key
|
||||
-> queue (if a run is active)
|
||||
-> agent run (streaming + tools)
|
||||
-> outbound replies (channel limits + chunking)
|
||||
```
|
||||
|
||||
Key knobs live in configuration:
|
||||
- `messages.*` for prefixes, queueing, and group behavior.
|
||||
- `agents.defaults.*` for block streaming and chunking defaults.
|
||||
- Channel overrides (`channels.whatsapp.*`, `channels.telegram.*`, etc.) for caps and streaming toggles.
|
||||
|
||||
See [Configuration](/gateway/configuration) for full schema.
|
||||
|
||||
## Inbound dedupe
|
||||
|
||||
Channels can redeliver the same message after reconnects. Bot keeps a
|
||||
short-lived cache keyed by channel/account/peer/session/message id so duplicate
|
||||
deliveries do not trigger another agent run.
|
||||
|
||||
## Inbound debouncing
|
||||
|
||||
Rapid consecutive messages from the **same sender** can be batched into a single
|
||||
agent turn via `messages.inbound`. Debouncing is scoped per channel + conversation
|
||||
and uses the most recent message for reply threading/IDs.
|
||||
|
||||
Config (global default + per-channel overrides):
|
||||
```json5
|
||||
{
|
||||
messages: {
|
||||
inbound: {
|
||||
debounceMs: 2000,
|
||||
byChannel: {
|
||||
whatsapp: 5000,
|
||||
slack: 1500,
|
||||
discord: 1500
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Debounce applies to **text-only** messages; media/attachments flush immediately.
|
||||
- Control commands bypass debouncing so they remain standalone.
|
||||
|
||||
## Sessions and devices
|
||||
|
||||
Sessions are owned by the gateway, not by clients.
|
||||
- Direct chats collapse into the agent main session key.
|
||||
- Groups/channels get their own session keys.
|
||||
- The session store and transcripts live on the gateway host.
|
||||
|
||||
Multiple devices/channels can map to the same session, but history is not fully
|
||||
synced back to every client. Recommendation: use one primary device for long
|
||||
conversations to avoid divergent context. The Control UI and TUI always show the
|
||||
gateway-backed session transcript, so they are the source of truth.
|
||||
|
||||
Details: [Session management](/concepts/session).
|
||||
|
||||
## Inbound bodies and history context
|
||||
|
||||
Bot separates the **prompt body** from the **command body**:
|
||||
- `Body`: prompt text sent to the agent. This may include channel envelopes and
|
||||
optional history wrappers.
|
||||
- `CommandBody`: raw user text for directive/command parsing.
|
||||
- `RawBody`: legacy alias for `CommandBody` (kept for compatibility).
|
||||
|
||||
When a channel supplies history, it uses a shared wrapper:
|
||||
- `[Chat messages since your last reply - for context]`
|
||||
- `[Current message - respond to this]`
|
||||
|
||||
For **non-direct chats** (groups/channels/rooms), the **current message body** is prefixed with the
|
||||
sender label (same style used for history entries). This keeps real-time and queued/history
|
||||
messages consistent in the agent prompt.
|
||||
|
||||
History buffers are **pending-only**: they include group messages that did *not*
|
||||
trigger a run (for example, mention-gated messages) and **exclude** messages
|
||||
already in the session transcript.
|
||||
|
||||
Directive stripping only applies to the **current message** section so history
|
||||
remains intact. Channels that wrap history should set `CommandBody` (or
|
||||
`RawBody`) to the original message text and keep `Body` as the combined prompt.
|
||||
History buffers are configurable via `messages.groupChat.historyLimit` (global
|
||||
default) and per-channel overrides like `channels.slack.historyLimit` or
|
||||
`channels.telegram.accounts.<id>.historyLimit` (set `0` to disable).
|
||||
|
||||
## Queueing and followups
|
||||
|
||||
If a run is already active, inbound messages can be queued, steered into the
|
||||
current run, or collected for a followup turn.
|
||||
|
||||
- Configure via `messages.queue` (and `messages.queue.byChannel`).
|
||||
- Modes: `interrupt`, `steer`, `followup`, `collect`, plus backlog variants.
|
||||
|
||||
Details: [Queueing](/concepts/queue).
|
||||
|
||||
## Streaming, chunking, and batching
|
||||
|
||||
Block streaming sends partial replies as the model produces text blocks.
|
||||
Chunking respects channel text limits and avoids splitting fenced code.
|
||||
|
||||
Key settings:
|
||||
- `agents.defaults.blockStreamingDefault` (`on|off`, default off)
|
||||
- `agents.defaults.blockStreamingBreak` (`text_end|message_end`)
|
||||
- `agents.defaults.blockStreamingChunk` (`minChars|maxChars|breakPreference`)
|
||||
- `agents.defaults.blockStreamingCoalesce` (idle-based batching)
|
||||
- `agents.defaults.humanDelay` (human-like pause between block replies)
|
||||
- Channel overrides: `*.blockStreaming` and `*.blockStreamingCoalesce` (non-Telegram channels require explicit `*.blockStreaming: true`)
|
||||
|
||||
Details: [Streaming + chunking](/concepts/streaming).
|
||||
|
||||
## Reasoning visibility and tokens
|
||||
|
||||
Bot can expose or hide model reasoning:
|
||||
- `/reasoning on|off|stream` controls visibility.
|
||||
- Reasoning content still counts toward token usage when produced by the model.
|
||||
- Telegram supports reasoning stream into the draft bubble.
|
||||
|
||||
Details: [Thinking + reasoning directives](/tools/thinking) and [Token use](/token-use).
|
||||
|
||||
## Prefixes, threading, and replies
|
||||
|
||||
Outbound message formatting is centralized in `messages`:
|
||||
- `messages.responsePrefix` (outbound prefix) and `channels.whatsapp.messagePrefix` (WhatsApp inbound prefix)
|
||||
- Reply threading via `replyToMode` and per-channel defaults
|
||||
|
||||
Details: [Configuration](/gateway/configuration#messages) and channel docs.
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"title": "Core Concepts",
|
||||
"pages": [
|
||||
"agent",
|
||||
"agent-loop",
|
||||
"agent-workspace",
|
||||
"architecture",
|
||||
"channel-routing",
|
||||
"compaction",
|
||||
"context",
|
||||
"group-messages",
|
||||
"groups",
|
||||
"markdown-formatting",
|
||||
"memory",
|
||||
"messages",
|
||||
"model-failover",
|
||||
"model-providers",
|
||||
"models",
|
||||
"multi-agent",
|
||||
"oauth",
|
||||
"presence",
|
||||
"queue",
|
||||
"retry",
|
||||
"session",
|
||||
"session-pruning",
|
||||
"session-tool",
|
||||
"sessions",
|
||||
"streaming",
|
||||
"system-prompt",
|
||||
"timezone",
|
||||
"typebox",
|
||||
"typing-indicators",
|
||||
"usage-tracking"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
title: "Model failover"
|
||||
description: "How Bot rotates auth profiles and falls back across models"
|
||||
---
|
||||
|
||||
Bot handles failures in two stages:
|
||||
1) **Auth profile rotation** within the current provider.
|
||||
2) **Model fallback** to the next model in `agents.defaults.model.fallbacks`.
|
||||
|
||||
This doc explains the runtime rules and the data that backs them.
|
||||
|
||||
## Auth storage (keys + OAuth)
|
||||
|
||||
Bot uses **auth profiles** for both API keys and OAuth tokens.
|
||||
|
||||
- Secrets live in `~/.bot/agents/<agentId>/agent/auth-profiles.json` (legacy: `~/.bot/agent/auth-profiles.json`).
|
||||
- Config `auth.profiles` / `auth.order` are **metadata + routing only** (no secrets).
|
||||
- Legacy import-only OAuth file: `~/.bot/credentials/oauth.json` (imported into `auth-profiles.json` on first use).
|
||||
|
||||
More detail: [/concepts/oauth](/concepts/oauth)
|
||||
|
||||
Credential types:
|
||||
- `type: "api_key"` → `{ provider, key }`
|
||||
- `type: "oauth"` → `{ provider, access, refresh, expires, email? }` (+ `projectId`/`enterpriseUrl` for some providers)
|
||||
|
||||
## Profile IDs
|
||||
|
||||
OAuth logins create distinct profiles so multiple accounts can coexist.
|
||||
- Default: `provider:default` when no email is available.
|
||||
- OAuth with email: `provider:<email>` (for example `google-antigravity:[email protected]`).
|
||||
|
||||
Profiles live in `~/.bot/agents/<agentId>/agent/auth-profiles.json` under `profiles`.
|
||||
|
||||
## Rotation order
|
||||
|
||||
When a provider has multiple profiles, Bot chooses an order like this:
|
||||
|
||||
1) **Explicit config**: `auth.order[provider]` (if set).
|
||||
2) **Configured profiles**: `auth.profiles` filtered by provider.
|
||||
3) **Stored profiles**: entries in `auth-profiles.json` for the provider.
|
||||
|
||||
If no explicit order is configured, Bot uses a round‑robin order:
|
||||
- **Primary key:** profile type (**OAuth before API keys**).
|
||||
- **Secondary key:** `usageStats.lastUsed` (oldest first, within each type).
|
||||
- **Cooldown/disabled profiles** are moved to the end, ordered by soonest expiry.
|
||||
|
||||
### Session stickiness (cache-friendly)
|
||||
|
||||
Bot **pins the chosen auth profile per session** to keep provider caches warm.
|
||||
It does **not** rotate on every request. The pinned profile is reused until:
|
||||
- the session is reset (`/new` / `/reset`)
|
||||
- a compaction completes (compaction count increments)
|
||||
- the profile is in cooldown/disabled
|
||||
|
||||
Manual selection via `/model …@<profileId>` sets a **user override** for that session
|
||||
and is not auto‑rotated until a new session starts.
|
||||
|
||||
Auto‑pinned profiles (selected by the session router) are treated as a **preference**:
|
||||
they are tried first, but Bot may rotate to another profile on rate limits/timeouts.
|
||||
User‑pinned profiles stay locked to that profile; if it fails and model fallbacks
|
||||
are configured, Bot moves to the next model instead of switching profiles.
|
||||
|
||||
### Why OAuth can “look lost”
|
||||
|
||||
If you have both an OAuth profile and an API key profile for the same provider, round‑robin can switch between them across messages unless pinned. To force a single profile:
|
||||
- Pin with `auth.order[provider] = ["provider:profileId"]`, or
|
||||
- Use a per-session override via `/model …` with a profile override (when supported by your UI/chat surface).
|
||||
|
||||
## Cooldowns
|
||||
|
||||
When a profile fails due to auth/rate‑limit errors (or a timeout that looks
|
||||
like rate limiting), Bot marks it in cooldown and moves to the next profile.
|
||||
Format/invalid‑request errors (for example Cloud Code Assist tool call ID
|
||||
validation failures) are treated as failover‑worthy and use the same cooldowns.
|
||||
|
||||
Cooldowns use exponential backoff:
|
||||
- 1 minute
|
||||
- 5 minutes
|
||||
- 25 minutes
|
||||
- 1 hour (cap)
|
||||
|
||||
State is stored in `auth-profiles.json` under `usageStats`:
|
||||
|
||||
```json
|
||||
{
|
||||
"usageStats": {
|
||||
"provider:profile": {
|
||||
"lastUsed": 1736160000000,
|
||||
"cooldownUntil": 1736160600000,
|
||||
"errorCount": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Billing disables
|
||||
|
||||
Billing/credit failures (for example “insufficient credits” / “credit balance too low”) are treated as failover‑worthy, but they’re usually not transient. Instead of a short cooldown, Bot marks the profile as **disabled** (with a longer backoff) and rotates to the next profile/provider.
|
||||
|
||||
State is stored in `auth-profiles.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"usageStats": {
|
||||
"provider:profile": {
|
||||
"disabledUntil": 1736178000000,
|
||||
"disabledReason": "billing"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Defaults:
|
||||
- Billing backoff starts at **5 hours**, doubles per billing failure, and caps at **24 hours**.
|
||||
- Backoff counters reset if the profile hasn’t failed for **24 hours** (configurable).
|
||||
|
||||
## Model fallback
|
||||
|
||||
If all profiles for a provider fail, Bot moves to the next model in
|
||||
`agents.defaults.model.fallbacks`. This applies to auth failures, rate limits, and
|
||||
timeouts that exhausted profile rotation (other errors do not advance fallback).
|
||||
|
||||
When a run starts with a model override (hooks or CLI), fallbacks still end at
|
||||
`agents.defaults.model.primary` after trying any configured fallbacks.
|
||||
|
||||
## Related config
|
||||
|
||||
See [Gateway configuration](/gateway/configuration) for:
|
||||
- `auth.profiles` / `auth.order`
|
||||
- `auth.cooldowns.billingBackoffHours` / `auth.cooldowns.billingBackoffHoursByProvider`
|
||||
- `auth.cooldowns.billingMaxHours` / `auth.cooldowns.failureWindowHours`
|
||||
- `agents.defaults.model.primary` / `agents.defaults.model.fallbacks`
|
||||
- `agents.defaults.imageModel` routing
|
||||
|
||||
See [Models](/concepts/models) for the broader model selection and fallback overview.
|
||||
@@ -0,0 +1,315 @@
|
||||
---
|
||||
title: "Model providers"
|
||||
description: "Model provider overview with example configs + CLI flows"
|
||||
---
|
||||
|
||||
This page covers **LLM/model providers** (not chat channels like WhatsApp/Telegram).
|
||||
For model selection rules, see [/concepts/models](/concepts/models).
|
||||
|
||||
## Quick rules
|
||||
|
||||
- Model refs use `provider/model` (example: `opencode/claude-opus-4-5`).
|
||||
- If you set `agents.defaults.models`, it becomes the allowlist.
|
||||
- CLI helpers: `bot onboard`, `bot models list`, `bot models set <provider/model>`.
|
||||
|
||||
## Built-in providers (pi-ai catalog)
|
||||
|
||||
Bot ships with the pi‑ai catalog. These providers require **no**
|
||||
`models.providers` config; just set auth + pick a model.
|
||||
|
||||
### OpenAI
|
||||
|
||||
- Provider: `openai`
|
||||
- Auth: `OPENAI_API_KEY`
|
||||
- Example model: `openai/gpt-5.2`
|
||||
- CLI: `bot onboard --auth-choice openai-api-key`
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: { defaults: { model: { primary: "openai/gpt-5.2" } } }
|
||||
}
|
||||
```
|
||||
|
||||
### Anthropic
|
||||
|
||||
- Provider: `anthropic`
|
||||
- Auth: `ANTHROPIC_API_KEY` or `claude setup-token`
|
||||
- Example model: `anthropic/claude-opus-4-5`
|
||||
- CLI: `bot onboard --auth-choice token` (paste setup-token) or `bot models auth paste-token --provider anthropic`
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: { defaults: { model: { primary: "anthropic/claude-opus-4-5" } } }
|
||||
}
|
||||
```
|
||||
|
||||
### OpenAI Code (Codex)
|
||||
|
||||
- Provider: `openai-codex`
|
||||
- Auth: OAuth or Codex CLI (`~/.codex/auth.json`)
|
||||
- Example model: `openai-codex/gpt-5.2`
|
||||
- CLI: `bot onboard --auth-choice openai-codex` or `codex-cli`
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: { defaults: { model: { primary: "openai-codex/gpt-5.2" } } }
|
||||
}
|
||||
```
|
||||
|
||||
### OpenCode Zen
|
||||
|
||||
- Provider: `opencode`
|
||||
- Auth: `OPENCODE_API_KEY` (or `OPENCODE_ZEN_API_KEY`)
|
||||
- Example model: `opencode/claude-opus-4-5`
|
||||
- CLI: `bot onboard --auth-choice opencode-zen`
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: { defaults: { model: { primary: "opencode/claude-opus-4-5" } } }
|
||||
}
|
||||
```
|
||||
|
||||
### Google Gemini (API key)
|
||||
|
||||
- Provider: `google`
|
||||
- Auth: `GEMINI_API_KEY`
|
||||
- Example model: `google/gemini-3-pro-preview`
|
||||
- CLI: `bot onboard --auth-choice gemini-api-key`
|
||||
|
||||
### Google Vertex / Antigravity / Gemini CLI
|
||||
|
||||
- Providers: `google-vertex`, `google-antigravity`, `google-gemini-cli`
|
||||
- Auth: Vertex uses gcloud ADC; Antigravity/Gemini CLI use their respective auth flows
|
||||
- Antigravity OAuth is shipped as a bundled plugin (`google-antigravity-auth`, disabled by default).
|
||||
- Enable: `bot plugins enable google-antigravity-auth`
|
||||
- Login: `bot models auth login --provider google-antigravity --set-default`
|
||||
- Gemini CLI OAuth is shipped as a bundled plugin (`google-gemini-cli-auth`, disabled by default).
|
||||
- Enable: `bot plugins enable google-gemini-cli-auth`
|
||||
- Login: `bot models auth login --provider google-gemini-cli --set-default`
|
||||
- Note: you do **not** paste a client id or secret into `bot.json`. The CLI login flow stores
|
||||
tokens in auth profiles on the gateway host.
|
||||
|
||||
### Z.AI (GLM)
|
||||
|
||||
- Provider: `zai`
|
||||
- Auth: `ZAI_API_KEY`
|
||||
- Example model: `zai/glm-4.7`
|
||||
- CLI: `bot onboard --auth-choice zai-api-key`
|
||||
- Aliases: `z.ai/*` and `z-ai/*` normalize to `zai/*`
|
||||
|
||||
### Vercel AI Gateway
|
||||
|
||||
- Provider: `vercel-ai-gateway`
|
||||
- Auth: `AI_GATEWAY_API_KEY`
|
||||
- Example model: `vercel-ai-gateway/anthropic/claude-opus-4.5`
|
||||
- CLI: `bot onboard --auth-choice ai-gateway-api-key`
|
||||
|
||||
### Other built-in providers
|
||||
|
||||
- OpenRouter: `openrouter` (`OPENROUTER_API_KEY`)
|
||||
- Example model: `openrouter/anthropic/claude-sonnet-4-5`
|
||||
- xAI: `xai` (`XAI_API_KEY`)
|
||||
- Groq: `groq` (`GROQ_API_KEY`)
|
||||
- Cerebras: `cerebras` (`CEREBRAS_API_KEY`)
|
||||
- GLM models on Cerebras use ids `zai-glm-4.7` and `zai-glm-4.6`.
|
||||
- OpenAI-compatible base URL: `https://api.cerebras.ai/v1`.
|
||||
- Mistral: `mistral` (`MISTRAL_API_KEY`)
|
||||
- GitHub Copilot: `github-copilot` (`COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN`)
|
||||
|
||||
## Providers via `models.providers` (custom/base URL)
|
||||
|
||||
Use `models.providers` (or `models.json`) to add **custom** providers or
|
||||
OpenAI/Anthropic‑compatible proxies.
|
||||
|
||||
### Moonshot AI (Kimi)
|
||||
|
||||
Moonshot uses OpenAI-compatible endpoints, so configure it as a custom provider:
|
||||
|
||||
- Provider: `moonshot`
|
||||
- Auth: `MOONSHOT_API_KEY`
|
||||
- Example model: `moonshot/kimi-k2-0905-preview`
|
||||
- Kimi K2 model IDs:
|
||||
{/* moonshot-kimi-k2-model-refs:start */}
|
||||
- `moonshot/kimi-k2-0905-preview`
|
||||
- `moonshot/kimi-k2-turbo-preview`
|
||||
- `moonshot/kimi-k2-thinking`
|
||||
- `moonshot/kimi-k2-thinking-turbo`
|
||||
{/* moonshot-kimi-k2-model-refs:end */}
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: { model: { primary: "moonshot/kimi-k2-0905-preview" } }
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
moonshot: {
|
||||
baseUrl: "https://api.moonshot.ai/v1",
|
||||
apiKey: "${MOONSHOT_API_KEY}",
|
||||
api: "openai-completions",
|
||||
models: [{ id: "kimi-k2-0905-preview", name: "Kimi K2 0905 Preview" }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Kimi Code
|
||||
|
||||
Kimi Code uses a dedicated endpoint and key (separate from Moonshot):
|
||||
|
||||
- Provider: `kimi-code`
|
||||
- Auth: `KIMICODE_API_KEY`
|
||||
- Example model: `kimi-code/kimi-for-coding`
|
||||
|
||||
```json5
|
||||
{
|
||||
env: { KIMICODE_API_KEY: "sk-..." },
|
||||
agents: {
|
||||
defaults: { model: { primary: "kimi-code/kimi-for-coding" } }
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
"kimi-code": {
|
||||
baseUrl: "https://api.kimi.com/coding/v1",
|
||||
apiKey: "${KIMICODE_API_KEY}",
|
||||
api: "openai-completions",
|
||||
models: [{ id: "kimi-for-coding", name: "Kimi For Coding" }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Qwen OAuth (free tier)
|
||||
|
||||
Qwen provides OAuth access to Qwen Coder + Vision via a device-code flow.
|
||||
Enable the bundled plugin, then log in:
|
||||
|
||||
```bash
|
||||
bot plugins enable qwen-portal-auth
|
||||
bot models auth login --provider qwen-portal --set-default
|
||||
```
|
||||
|
||||
Model refs:
|
||||
- `qwen-portal/coder-model`
|
||||
- `qwen-portal/vision-model`
|
||||
|
||||
See [/providers/qwen](/providers/qwen) for setup details and notes.
|
||||
|
||||
### Synthetic
|
||||
|
||||
Synthetic provides Anthropic-compatible models behind the `synthetic` provider:
|
||||
|
||||
- Provider: `synthetic`
|
||||
- Auth: `SYNTHETIC_API_KEY`
|
||||
- Example model: `synthetic/hf:MiniMaxAI/MiniMax-M2.1`
|
||||
- CLI: `bot onboard --auth-choice synthetic-api-key`
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: { model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.1" } }
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
synthetic: {
|
||||
baseUrl: "https://api.synthetic.new/anthropic",
|
||||
apiKey: "${SYNTHETIC_API_KEY}",
|
||||
api: "anthropic-messages",
|
||||
models: [{ id: "hf:MiniMaxAI/MiniMax-M2.1", name: "MiniMax M2.1" }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### MiniMax
|
||||
|
||||
MiniMax is configured via `models.providers` because it uses custom endpoints:
|
||||
|
||||
- MiniMax (Anthropic‑compatible): `--auth-choice minimax-api`
|
||||
- Auth: `MINIMAX_API_KEY`
|
||||
|
||||
See [/providers/minimax](/providers/minimax) for setup details, model options, and config snippets.
|
||||
|
||||
### Ollama
|
||||
|
||||
Ollama is a local LLM runtime that provides an OpenAI-compatible API:
|
||||
|
||||
- Provider: `ollama`
|
||||
- Auth: None required (local server)
|
||||
- Example model: `ollama/llama3.3`
|
||||
- Installation: https://ollama.ai
|
||||
|
||||
```bash
|
||||
# Install Ollama, then pull a model:
|
||||
ollama pull llama3.3
|
||||
```
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: { model: { primary: "ollama/llama3.3" } }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ollama is automatically detected when running locally at `http://127.0.0.1:11434/v1`. See [/providers/ollama](/providers/ollama) for model recommendations and custom configuration.
|
||||
|
||||
### Local proxies (LM Studio, vLLM, LiteLLM, etc.)
|
||||
|
||||
Example (OpenAI‑compatible):
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "lmstudio/minimax-m2.1-gs32" },
|
||||
models: { "lmstudio/minimax-m2.1-gs32": { alias: "Minimax" } }
|
||||
}
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
lmstudio: {
|
||||
baseUrl: "http://localhost:1234/v1",
|
||||
apiKey: "LMSTUDIO_KEY",
|
||||
api: "openai-completions",
|
||||
models: [
|
||||
{
|
||||
id: "minimax-m2.1-gs32",
|
||||
name: "MiniMax M2.1",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 8192
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- For custom providers, `reasoning`, `input`, `cost`, `contextWindow`, and `maxTokens` are optional.
|
||||
When omitted, Bot defaults to:
|
||||
- `reasoning: false`
|
||||
- `input: ["text"]`
|
||||
- `cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }`
|
||||
- `contextWindow: 200000`
|
||||
- `maxTokens: 8192`
|
||||
- Recommended: set explicit values that match your proxy/model limits.
|
||||
|
||||
## CLI examples
|
||||
|
||||
```bash
|
||||
bot onboard --auth-choice opencode-zen
|
||||
bot models set opencode/claude-opus-4-5
|
||||
bot models list
|
||||
```
|
||||
|
||||
See also: [/gateway/configuration](/gateway/configuration) for full configuration examples.
|
||||
@@ -0,0 +1,198 @@
|
||||
---
|
||||
title: "Models CLI"
|
||||
description: "Models CLI: list, set, aliases, fallbacks, scan, status"
|
||||
---
|
||||
|
||||
See [/concepts/model-failover](/concepts/model-failover) for auth profile
|
||||
rotation, cooldowns, and how that interacts with fallbacks.
|
||||
Quick provider overview + examples: [/concepts/model-providers](/concepts/model-providers).
|
||||
|
||||
## How model selection works
|
||||
|
||||
Bot selects models in this order:
|
||||
|
||||
1) **Primary** model (`agents.defaults.model.primary` or `agents.defaults.model`).
|
||||
2) **Fallbacks** in `agents.defaults.model.fallbacks` (in order).
|
||||
3) **Provider auth failover** happens inside a provider before moving to the
|
||||
next model.
|
||||
|
||||
Related:
|
||||
- `agents.defaults.models` is the allowlist/catalog of models Bot can use (plus aliases).
|
||||
- `agents.defaults.imageModel` is used **only when** the primary model can’t accept images.
|
||||
- Per-agent defaults can override `agents.defaults.model` via `agents.list[].model` plus bindings (see [/concepts/multi-agent](/concepts/multi-agent)).
|
||||
|
||||
## Quick model picks (anecdotal)
|
||||
|
||||
- **GLM**: a bit better for coding/tool calling.
|
||||
- **MiniMax**: better for writing and vibes.
|
||||
|
||||
## Setup wizard (recommended)
|
||||
|
||||
If you don’t want to hand-edit config, run the onboarding wizard:
|
||||
|
||||
```bash
|
||||
bot onboard
|
||||
```
|
||||
|
||||
It can set up model + auth for common providers, including **OpenAI Code (Codex)
|
||||
subscription** (OAuth) and **Anthropic** (API key recommended; `claude
|
||||
setup-token` also supported).
|
||||
|
||||
## Config keys (overview)
|
||||
|
||||
- `agents.defaults.model.primary` and `agents.defaults.model.fallbacks`
|
||||
- `agents.defaults.imageModel.primary` and `agents.defaults.imageModel.fallbacks`
|
||||
- `agents.defaults.models` (allowlist + aliases + provider params)
|
||||
- `models.providers` (custom providers written into `models.json`)
|
||||
|
||||
Model refs are normalized to lowercase. Provider aliases like `z.ai/*` normalize
|
||||
to `zai/*`.
|
||||
|
||||
Provider configuration examples (including OpenCode Zen) live in
|
||||
[/gateway/configuration](/gateway/configuration#opencode-zen-multi-model-proxy).
|
||||
|
||||
## “Model is not allowed” (and why replies stop)
|
||||
|
||||
If `agents.defaults.models` is set, it becomes the **allowlist** for `/model` and for
|
||||
session overrides. When a user selects a model that isn’t in that allowlist,
|
||||
Bot returns:
|
||||
|
||||
```
|
||||
Model "provider/model" is not allowed. Use /model to list available models.
|
||||
```
|
||||
|
||||
This happens **before** a normal reply is generated, so the message can feel
|
||||
like it “didn’t respond.” The fix is to either:
|
||||
|
||||
- Add the model to `agents.defaults.models`, or
|
||||
- Clear the allowlist (remove `agents.defaults.models`), or
|
||||
- Pick a model from `/model list`.
|
||||
|
||||
Example allowlist config:
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
model: { primary: "anthropic/claude-sonnet-4-5" },
|
||||
models: {
|
||||
"anthropic/claude-sonnet-4-5": { alias: "Sonnet" },
|
||||
"anthropic/claude-opus-4-5": { alias: "Opus" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Switching models in chat (`/model`)
|
||||
|
||||
You can switch models for the current session without restarting:
|
||||
|
||||
```
|
||||
/model
|
||||
/model list
|
||||
/model 3
|
||||
/model openai/gpt-5.2
|
||||
/model status
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `/model` (and `/model list`) is a compact, numbered picker (model family + available providers).
|
||||
- `/model <#>` selects from that picker.
|
||||
- `/model status` is the detailed view (auth candidates and, when configured, provider endpoint `baseUrl` + `api` mode).
|
||||
- Model refs are parsed by splitting on the **first** `/`. Use `provider/model` when typing `/model <ref>`.
|
||||
- If the model ID itself contains `/` (OpenRouter-style), you must include the provider prefix (example: `/model openrouter/moonshotai/kimi-k2`).
|
||||
- If you omit the provider, Bot treats the input as an alias or a model for the **default provider** (only works when there is no `/` in the model ID).
|
||||
|
||||
Full command behavior/config: [Slash commands](/tools/slash-commands).
|
||||
|
||||
## CLI commands
|
||||
|
||||
```bash
|
||||
bot models list
|
||||
bot models status
|
||||
bot models set <provider/model>
|
||||
bot models set-image <provider/model>
|
||||
|
||||
bot models aliases list
|
||||
bot models aliases add <alias> <provider/model>
|
||||
bot models aliases remove <alias>
|
||||
|
||||
bot models fallbacks list
|
||||
bot models fallbacks add <provider/model>
|
||||
bot models fallbacks remove <provider/model>
|
||||
bot models fallbacks clear
|
||||
|
||||
bot models image-fallbacks list
|
||||
bot models image-fallbacks add <provider/model>
|
||||
bot models image-fallbacks remove <provider/model>
|
||||
bot models image-fallbacks clear
|
||||
```
|
||||
|
||||
`bot models` (no subcommand) is a shortcut for `models status`.
|
||||
|
||||
### `models list`
|
||||
|
||||
Shows configured models by default. Useful flags:
|
||||
|
||||
- `--all`: full catalog
|
||||
- `--local`: local providers only
|
||||
- `--provider <name>`: filter by provider
|
||||
- `--plain`: one model per line
|
||||
- `--json`: machine‑readable output
|
||||
|
||||
### `models status`
|
||||
|
||||
Shows the resolved primary model, fallbacks, image model, and an auth overview
|
||||
of configured providers. It also surfaces OAuth expiry status for profiles found
|
||||
in the auth store (warns within 24h by default). `--plain` prints only the
|
||||
resolved primary model.
|
||||
OAuth status is always shown (and included in `--json` output). If a configured
|
||||
provider has no credentials, `models status` prints a **Missing auth** section.
|
||||
JSON includes `auth.oauth` (warn window + profiles) and `auth.providers`
|
||||
(effective auth per provider).
|
||||
Use `--check` for automation (exit `1` when missing/expired, `2` when expiring).
|
||||
|
||||
Preferred Anthropic auth is the Claude Code CLI setup-token (run anywhere; paste on the gateway host if needed):
|
||||
|
||||
```bash
|
||||
claude setup-token
|
||||
bot models status
|
||||
```
|
||||
|
||||
## Scanning (OpenRouter free models)
|
||||
|
||||
`bot models scan` inspects OpenRouter’s **free model catalog** and can
|
||||
optionally probe models for tool and image support.
|
||||
|
||||
Key flags:
|
||||
|
||||
- `--no-probe`: skip live probes (metadata only)
|
||||
- `--min-params <b>`: minimum parameter size (billions)
|
||||
- `--max-age-days <days>`: skip older models
|
||||
- `--provider <name>`: provider prefix filter
|
||||
- `--max-candidates <n>`: fallback list size
|
||||
- `--set-default`: set `agents.defaults.model.primary` to the first selection
|
||||
- `--set-image`: set `agents.defaults.imageModel.primary` to the first image selection
|
||||
|
||||
Probing requires an OpenRouter API key (from auth profiles or
|
||||
`OPENROUTER_API_KEY`). Without a key, use `--no-probe` to list candidates only.
|
||||
|
||||
Scan results are ranked by:
|
||||
1) Image support
|
||||
2) Tool latency
|
||||
3) Context size
|
||||
4) Parameter count
|
||||
|
||||
Input
|
||||
- OpenRouter `/models` list (filter `:free`)
|
||||
- Requires OpenRouter API key from auth profiles or `OPENROUTER_API_KEY` (see [/environment](/environment))
|
||||
- Optional filters: `--max-age-days`, `--min-params`, `--provider`, `--max-candidates`
|
||||
- Probe controls: `--timeout`, `--concurrency`
|
||||
|
||||
When run in a TTY, you can select fallbacks interactively. In non‑interactive
|
||||
mode, pass `--yes` to accept defaults.
|
||||
|
||||
## Models registry (`models.json`)
|
||||
|
||||
Custom providers in `models.providers` are written into `models.json` under the
|
||||
agent directory (default `~/.bot/agents/<agentId>/models.json`). This file
|
||||
is merged by default unless `models.mode` is set to `replace`.
|
||||
@@ -0,0 +1,350 @@
|
||||
---
|
||||
title: "Multi-Agent Routing"
|
||||
description: "Multi-agent routing: isolated agents, channel accounts, and bindings"
|
||||
---
|
||||
|
||||
Goal: multiple *isolated* agents (separate workspace + `agentDir` + sessions), plus multiple channel accounts (e.g. two WhatsApps) in one running Gateway. Inbound is routed to an agent via bindings.
|
||||
|
||||
## What is “one agent”?
|
||||
|
||||
An **agent** is a fully scoped brain with its own:
|
||||
|
||||
- **Workspace** (files, AGENTS.md/SOUL.md/USER.md, local notes, persona rules).
|
||||
- **State directory** (`agentDir`) for auth profiles, model registry, and per-agent config.
|
||||
- **Session store** (chat history + routing state) under `~/.bot/agents/<agentId>/sessions`.
|
||||
|
||||
Auth profiles are **per-agent**. Each agent reads from its own:
|
||||
|
||||
```
|
||||
~/.bot/agents/<agentId>/agent/auth-profiles.json
|
||||
```
|
||||
|
||||
Main agent credentials are **not** shared automatically. Never reuse `agentDir`
|
||||
across agents (it causes auth/session collisions). If you want to share creds,
|
||||
copy `auth-profiles.json` into the other agent's `agentDir`.
|
||||
|
||||
Skills are per-agent via each workspace’s `skills/` folder, with shared skills
|
||||
available from `~/.bot/skills`. See [Skills: per-agent vs shared](/tools/skills#per-agent-vs-shared-skills).
|
||||
|
||||
The Gateway can host **one agent** (default) or **many agents** side-by-side.
|
||||
|
||||
**Workspace note:** each agent’s workspace is the **default cwd**, not a hard
|
||||
sandbox. Relative paths resolve inside the workspace, but absolute paths can
|
||||
reach other host locations unless sandboxing is enabled. See
|
||||
[Sandboxing](/gateway/sandboxing).
|
||||
|
||||
## Paths (quick map)
|
||||
|
||||
- Config: `~/.bot/bot.json` (or `BOT_CONFIG_PATH`)
|
||||
- State dir: `~/.bot` (or `BOT_STATE_DIR`)
|
||||
- Workspace: `~/bot` (or `~/bot-<agentId>`)
|
||||
- Agent dir: `~/.bot/agents/<agentId>/agent` (or `agents.list[].agentDir`)
|
||||
- Sessions: `~/.bot/agents/<agentId>/sessions`
|
||||
|
||||
### Single-agent mode (default)
|
||||
|
||||
If you do nothing, Bot runs a single agent:
|
||||
|
||||
- `agentId` defaults to **`main`**.
|
||||
- Sessions are keyed as `agent:main:<mainKey>`.
|
||||
- Workspace defaults to `~/bot` (or `~/bot-<profile>` when `BOT_PROFILE` is set).
|
||||
- State defaults to `~/.bot/agents/main/agent`.
|
||||
|
||||
## Agent helper
|
||||
|
||||
Use the agent wizard to add a new isolated agent:
|
||||
|
||||
```bash
|
||||
bot agents add work
|
||||
```
|
||||
|
||||
Then add `bindings` (or let the wizard do it) to route inbound messages.
|
||||
|
||||
Verify with:
|
||||
|
||||
```bash
|
||||
bot agents list --bindings
|
||||
```
|
||||
|
||||
## Multiple agents = multiple people, multiple personalities
|
||||
|
||||
With **multiple agents**, each `agentId` becomes a **fully isolated persona**:
|
||||
|
||||
- **Different phone numbers/accounts** (per channel `accountId`).
|
||||
- **Different personalities** (per-agent workspace files like `AGENTS.md` and `SOUL.md`).
|
||||
- **Separate auth + sessions** (no cross-talk unless explicitly enabled).
|
||||
|
||||
This lets **multiple people** share one Gateway server while keeping their AI “brains” and data isolated.
|
||||
|
||||
## One WhatsApp number, multiple people (DM split)
|
||||
|
||||
You can route **different WhatsApp DMs** to different agents while staying on **one WhatsApp account**. Match on sender E.164 (like `+15551234567`) with `peer.kind: "dm"`. Replies still come from the same WhatsApp number (no per‑agent sender identity).
|
||||
|
||||
Important detail: direct chats collapse to the agent’s **main session key**, so true isolation requires **one agent per person**.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "alex", workspace: "~/bot-alex" },
|
||||
{ id: "mia", workspace: "~/bot-mia" }
|
||||
]
|
||||
},
|
||||
bindings: [
|
||||
{ agentId: "alex", match: { channel: "whatsapp", peer: { kind: "dm", id: "+15551230001" } } },
|
||||
{ agentId: "mia", match: { channel: "whatsapp", peer: { kind: "dm", id: "+15551230002" } } }
|
||||
],
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["+15551230001", "+15551230002"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- DM access control is **global per WhatsApp account** (pairing/allowlist), not per agent.
|
||||
- For shared groups, bind the group to one agent or use [Broadcast groups](/broadcast-groups).
|
||||
|
||||
## Routing rules (how messages pick an agent)
|
||||
|
||||
Bindings are **deterministic** and **most-specific wins**:
|
||||
|
||||
1. `peer` match (exact DM/group/channel id)
|
||||
2. `guildId` (Discord)
|
||||
3. `teamId` (Slack)
|
||||
4. `accountId` match for a channel
|
||||
5. channel-level match (`accountId: "*"`)
|
||||
6. fallback to default agent (`agents.list[].default`, else first list entry, default: `main`)
|
||||
|
||||
## Multiple accounts / phone numbers
|
||||
|
||||
Channels that support **multiple accounts** (e.g. WhatsApp) use `accountId` to identify
|
||||
each login. Each `accountId` can be routed to a different agent, so one server can host
|
||||
multiple phone numbers without mixing sessions.
|
||||
|
||||
## Concepts
|
||||
|
||||
- `agentId`: one “brain” (workspace, per-agent auth, per-agent session store).
|
||||
- `accountId`: one channel account instance (e.g. WhatsApp account `"personal"` vs `"biz"`).
|
||||
- `binding`: routes inbound messages to an `agentId` by `(channel, accountId, peer)` and optionally guild/team ids.
|
||||
- Direct chats collapse to `agent:<agentId>:<mainKey>` (per-agent “main”; `session.mainKey`).
|
||||
|
||||
## Example: two WhatsApps → two agents
|
||||
|
||||
`~/.bot/bot.json` (JSON5):
|
||||
|
||||
```js
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "home",
|
||||
default: true,
|
||||
name: "Home",
|
||||
workspace: "~/bot-home",
|
||||
agentDir: "~/.bot/agents/home/agent",
|
||||
},
|
||||
{
|
||||
id: "work",
|
||||
name: "Work",
|
||||
workspace: "~/bot-work",
|
||||
agentDir: "~/.bot/agents/work/agent",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Deterministic routing: first match wins (most-specific first).
|
||||
bindings: [
|
||||
{ agentId: "home", match: { channel: "whatsapp", accountId: "personal" } },
|
||||
{ agentId: "work", match: { channel: "whatsapp", accountId: "biz" } },
|
||||
|
||||
// Optional per-peer override (example: send a specific group to work agent).
|
||||
{
|
||||
agentId: "work",
|
||||
match: {
|
||||
channel: "whatsapp",
|
||||
accountId: "personal",
|
||||
peer: { kind: "group", id: "[email protected]" },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// Off by default: agent-to-agent messaging must be explicitly enabled + allowlisted.
|
||||
tools: {
|
||||
agentToAgent: {
|
||||
enabled: false,
|
||||
allow: ["home", "work"],
|
||||
},
|
||||
},
|
||||
|
||||
channels: {
|
||||
whatsapp: {
|
||||
accounts: {
|
||||
personal: {
|
||||
// Optional override. Default: ~/.bot/credentials/whatsapp/personal
|
||||
// authDir: "~/.bot/credentials/whatsapp/personal",
|
||||
},
|
||||
biz: {
|
||||
// Optional override. Default: ~/.bot/credentials/whatsapp/biz
|
||||
// authDir: "~/.bot/credentials/whatsapp/biz",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Example: WhatsApp daily chat + Telegram deep work
|
||||
|
||||
Split by channel: route WhatsApp to a fast everyday agent and Telegram to an Opus agent.
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "chat",
|
||||
name: "Everyday",
|
||||
workspace: "~/bot-chat",
|
||||
model: "anthropic/claude-sonnet-4-5"
|
||||
},
|
||||
{
|
||||
id: "opus",
|
||||
name: "Deep Work",
|
||||
workspace: "~/bot-opus",
|
||||
model: "anthropic/claude-opus-4-5"
|
||||
}
|
||||
]
|
||||
},
|
||||
bindings: [
|
||||
{ agentId: "chat", match: { channel: "whatsapp" } },
|
||||
{ agentId: "opus", match: { channel: "telegram" } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- If you have multiple accounts for a channel, add `accountId` to the binding (for example `{ channel: "whatsapp", accountId: "personal" }`).
|
||||
- To route a single DM/group to Opus while keeping the rest on chat, add a `match.peer` binding for that peer; peer matches always win over channel-wide rules.
|
||||
|
||||
## Example: same channel, one peer to Opus
|
||||
|
||||
Keep WhatsApp on the fast agent, but route one DM to Opus:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "chat", name: "Everyday", workspace: "~/bot-chat", model: "anthropic/claude-sonnet-4-5" },
|
||||
{ id: "opus", name: "Deep Work", workspace: "~/bot-opus", model: "anthropic/claude-opus-4-5" }
|
||||
]
|
||||
},
|
||||
bindings: [
|
||||
{ agentId: "opus", match: { channel: "whatsapp", peer: { kind: "dm", id: "+15551234567" } } },
|
||||
{ agentId: "chat", match: { channel: "whatsapp" } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Peer bindings always win, so keep them above the channel-wide rule.
|
||||
|
||||
## Family agent bound to a WhatsApp group
|
||||
|
||||
Bind a dedicated family agent to a single WhatsApp group, with mention gating
|
||||
and a tighter tool policy:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "family",
|
||||
name: "Family",
|
||||
workspace: "~/bot-family",
|
||||
identity: { name: "Family Bot" },
|
||||
groupChat: {
|
||||
mentionPatterns: ["@family", "@familybot", "@Family Bot"]
|
||||
},
|
||||
sandbox: {
|
||||
mode: "all",
|
||||
scope: "agent"
|
||||
},
|
||||
tools: {
|
||||
allow: ["exec", "read", "sessions_list", "sessions_history", "sessions_send", "sessions_spawn", "session_status"],
|
||||
deny: ["write", "edit", "apply_patch", "browser", "canvas", "nodes", "cron"]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
agentId: "family",
|
||||
match: {
|
||||
channel: "whatsapp",
|
||||
peer: { kind: "group", id: "[email protected]" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Tool allow/deny lists are **tools**, not skills. If a skill needs to run a
|
||||
binary, ensure `exec` is allowed and the binary exists in the sandbox.
|
||||
- For stricter gating, set `agents.list[].groupChat.mentionPatterns` and keep
|
||||
group allowlists enabled for the channel.
|
||||
|
||||
## Per-Agent Sandbox and Tool Configuration
|
||||
|
||||
Starting with v2026.1.6, each agent can have its own sandbox and tool restrictions:
|
||||
|
||||
```js
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "personal",
|
||||
workspace: "~/bot-personal",
|
||||
sandbox: {
|
||||
mode: "off", // No sandbox for personal agent
|
||||
},
|
||||
// No tool restrictions - all tools available
|
||||
},
|
||||
{
|
||||
id: "family",
|
||||
workspace: "~/bot-family",
|
||||
sandbox: {
|
||||
mode: "all", // Always sandboxed
|
||||
scope: "agent", // One container per agent
|
||||
docker: {
|
||||
// Optional one-time setup after container creation
|
||||
setupCommand: "apt-get update && apt-get install -y git curl",
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
allow: ["read"], // Only read tool
|
||||
deny: ["exec", "write", "edit", "apply_patch"], // Deny others
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Note: `setupCommand` lives under `sandbox.docker` and runs once on container creation.
|
||||
Per-agent `sandbox.docker.*` overrides are ignored when the resolved scope is `"shared"`.
|
||||
|
||||
**Benefits:**
|
||||
- **Security isolation**: Restrict tools for untrusted agents
|
||||
- **Resource control**: Sandbox specific agents while keeping others on host
|
||||
- **Flexible policies**: Different permissions per agent
|
||||
|
||||
Note: `tools.elevated` is **global** and sender-based; it is not configurable per agent.
|
||||
If you need per-agent boundaries, use `agents.list[].tools` to deny `exec`.
|
||||
For group targeting, use `agents.list[].groupChat.mentionPatterns` so @mentions map cleanly to the intended agent.
|
||||
|
||||
See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for detailed examples.
|
||||
@@ -0,0 +1,157 @@
|
||||
---
|
||||
title: "OAuth"
|
||||
description: "OAuth in Bot: token exchange, storage, CLI sync, and multi-account patterns"
|
||||
---
|
||||
|
||||
Bot supports “subscription auth” via OAuth for providers that offer it (notably **Anthropic (Claude Pro/Max)** and **OpenAI Codex (ChatGPT OAuth)**). This page explains:
|
||||
|
||||
- how the OAuth **token exchange** works (PKCE)
|
||||
- where tokens are **stored** (and why)
|
||||
- how we **reuse external CLI tokens** (Claude Code / Codex CLI)
|
||||
- how to handle **multiple accounts** (profiles + per-session overrides)
|
||||
|
||||
Bot also supports **provider plugins** that ship their own OAuth or API‑key
|
||||
flows. Run them via:
|
||||
|
||||
```bash
|
||||
bot models auth login --provider <id>
|
||||
```
|
||||
|
||||
## The token sink (why it exists)
|
||||
|
||||
OAuth providers commonly mint a **new refresh token** during login/refresh flows. Some providers (or OAuth clients) can invalidate older refresh tokens when a new one is issued for the same user/app.
|
||||
|
||||
Practical symptom:
|
||||
- you log in via Bot *and* via Claude Code / Codex CLI → one of them randomly gets “logged out” later
|
||||
|
||||
To reduce that, Bot treats `auth-profiles.json` as a **token sink**:
|
||||
- the runtime reads credentials from **one place**
|
||||
- we can **sync in** credentials from external CLIs instead of doing a second login
|
||||
- we can keep multiple profiles and route them deterministically
|
||||
|
||||
## Storage (where tokens live)
|
||||
|
||||
Secrets are stored **per-agent**:
|
||||
|
||||
- Auth profiles (OAuth + API keys): `~/.bot/agents/<agentId>/agent/auth-profiles.json`
|
||||
- Runtime cache (managed automatically; don’t edit): `~/.bot/agents/<agentId>/agent/auth.json`
|
||||
|
||||
Legacy import-only file (still supported, but not the main store):
|
||||
- `~/.bot/credentials/oauth.json` (imported into `auth-profiles.json` on first use)
|
||||
|
||||
All of the above also respect `$BOT_STATE_DIR` (state dir override). Full reference: [/gateway/configuration](/gateway/configuration#auth-storage-oauth--api-keys)
|
||||
|
||||
## Reusing Claude Code / Codex CLI OAuth tokens (recommended)
|
||||
|
||||
If you already signed in with the external CLIs *on the gateway host*, Bot can reuse those tokens without starting a separate OAuth flow:
|
||||
|
||||
- Claude Code: `anthropic:claude-cli`
|
||||
- macOS: Keychain item "Claude Code-credentials" (choose "Always Allow" to avoid launchd prompts)
|
||||
- Linux/Windows: `~/.claude/.credentials.json`
|
||||
- Codex CLI: reads `~/.codex/auth.json` → profile `openai-codex:codex-cli`
|
||||
|
||||
Sync happens when Bot loads the auth store (so it stays up-to-date when the CLIs refresh tokens).
|
||||
On macOS, the first read may trigger a Keychain prompt; run `bot models status`
|
||||
in a terminal once if the Gateway runs headless and can’t access the entry.
|
||||
|
||||
How to verify:
|
||||
|
||||
```bash
|
||||
bot models status
|
||||
bot channels list
|
||||
```
|
||||
|
||||
Or JSON:
|
||||
|
||||
```bash
|
||||
bot channels list --json
|
||||
```
|
||||
|
||||
## OAuth exchange (how login works)
|
||||
|
||||
Bot’s interactive login flows are implemented in `@mariozechner/pi-ai` and wired into the wizards/commands.
|
||||
|
||||
### Anthropic (Claude Pro/Max)
|
||||
|
||||
Flow shape (PKCE):
|
||||
|
||||
1) generate PKCE verifier/challenge
|
||||
2) open `https://claude.ai/oauth/authorize?...`
|
||||
3) user pastes `code#state`
|
||||
4) exchange at `https://console.anthropic.com/v1/oauth/token`
|
||||
5) store `{ access, refresh, expires }` under an auth profile
|
||||
|
||||
The wizard path is `bot onboard` → auth choice `oauth` (Anthropic).
|
||||
|
||||
### OpenAI Codex (ChatGPT OAuth)
|
||||
|
||||
Flow shape (PKCE):
|
||||
|
||||
1) generate PKCE verifier/challenge + random `state`
|
||||
2) open `https://auth.openai.com/oauth/authorize?...`
|
||||
3) try to capture callback on `http://127.0.0.1:1455/auth/callback`
|
||||
4) if callback can’t bind (or you’re remote/headless), paste the redirect URL/code
|
||||
5) exchange at `https://auth.openai.com/oauth/token`
|
||||
6) extract `accountId` from the access token and store `{ access, refresh, expires, accountId }`
|
||||
|
||||
Wizard path is `bot onboard` → auth choice `openai-codex` (or `codex-cli` to reuse an existing Codex CLI login).
|
||||
|
||||
## Refresh + expiry
|
||||
|
||||
Profiles store an `expires` timestamp.
|
||||
|
||||
At runtime:
|
||||
- if `expires` is in the future → use the stored access token
|
||||
- if expired → refresh (under a file lock) and overwrite the stored credentials
|
||||
|
||||
The refresh flow is automatic; you generally don't need to manage tokens manually.
|
||||
|
||||
### Bidirectional sync with Claude Code
|
||||
|
||||
When Bot refreshes an Anthropic OAuth token (profile `anthropic:claude-cli`), it **writes the new credentials back** to Claude Code's storage:
|
||||
|
||||
- **Linux/Windows**: updates `~/.claude/.credentials.json`
|
||||
- **macOS**: updates Keychain item "Claude Code-credentials"
|
||||
|
||||
This ensures both tools stay in sync and neither gets "logged out" after the other refreshes.
|
||||
|
||||
**Why this matters for long-running agents:**
|
||||
|
||||
Anthropic OAuth tokens expire after a few hours. Without bidirectional sync:
|
||||
1. Bot refreshes the token → gets new access token
|
||||
2. Claude Code still has the old token → gets logged out
|
||||
|
||||
With bidirectional sync, both tools always have the latest valid token, enabling autonomous operation for days or weeks without manual intervention.
|
||||
|
||||
## Multiple accounts (profiles) + routing
|
||||
|
||||
Two patterns:
|
||||
|
||||
### 1) Preferred: separate agents
|
||||
|
||||
If you want “personal” and “work” to never interact, use isolated agents (separate sessions + credentials + workspace):
|
||||
|
||||
```bash
|
||||
bot agents add work
|
||||
bot agents add personal
|
||||
```
|
||||
|
||||
Then configure auth per-agent (wizard) and route chats to the right agent.
|
||||
|
||||
### 2) Advanced: multiple profiles in one agent
|
||||
|
||||
`auth-profiles.json` supports multiple profile IDs for the same provider.
|
||||
|
||||
Pick which profile is used:
|
||||
- globally via config ordering (`auth.order`)
|
||||
- per-session via `/model ...@<profileId>`
|
||||
|
||||
Example (session override):
|
||||
- `/model Opus@anthropic:work`
|
||||
|
||||
How to see what profile IDs exist:
|
||||
- `bot channels list --json` (shows `auth[]`)
|
||||
|
||||
Related docs:
|
||||
- [/concepts/model-failover](/concepts/model-failover) (rotation + cooldown rules)
|
||||
- [/tools/slash-commands](/tools/slash-commands) (command surface)
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
title: "Presence"
|
||||
description: "How Bot presence entries are produced, merged, and displayed"
|
||||
---
|
||||
|
||||
Bot “presence” is a lightweight, best‑effort view of:
|
||||
- the **Gateway** itself, and
|
||||
- **clients connected to the Gateway** (mac app, WebChat, CLI, etc.)
|
||||
|
||||
Presence is used primarily to render the macOS app’s **Instances** tab and to
|
||||
provide quick operator visibility.
|
||||
|
||||
## Presence fields (what shows up)
|
||||
|
||||
Presence entries are structured objects with fields like:
|
||||
|
||||
- `instanceId` (optional but strongly recommended): stable client identity (usually `connect.client.instanceId`)
|
||||
- `host`: human‑friendly host name
|
||||
- `ip`: best‑effort IP address
|
||||
- `version`: client version string
|
||||
- `deviceFamily` / `modelIdentifier`: hardware hints
|
||||
- `mode`: `ui`, `webchat`, `cli`, `backend`, `probe`, `test`, `node`, ...
|
||||
- `lastInputSeconds`: “seconds since last user input” (if known)
|
||||
- `reason`: `self`, `connect`, `node-connected`, `periodic`, ...
|
||||
- `ts`: last update timestamp (ms since epoch)
|
||||
|
||||
## Producers (where presence comes from)
|
||||
|
||||
Presence entries are produced by multiple sources and **merged**.
|
||||
|
||||
### 1) Gateway self entry
|
||||
|
||||
The Gateway always seeds a “self” entry at startup so UIs show the gateway host
|
||||
even before any clients connect.
|
||||
|
||||
### 2) WebSocket connect
|
||||
|
||||
Every WS client begins with a `connect` request. On successful handshake the
|
||||
Gateway upserts a presence entry for that connection.
|
||||
|
||||
#### Why one‑off CLI commands don’t show up
|
||||
|
||||
The CLI often connects for short, one‑off commands. To avoid spamming the
|
||||
Instances list, `client.mode === "cli"` is **not** turned into a presence entry.
|
||||
|
||||
### 3) `system-event` beacons
|
||||
|
||||
Clients can send richer periodic beacons via the `system-event` method. The mac
|
||||
app uses this to report host name, IP, and `lastInputSeconds`.
|
||||
|
||||
### 4) Node connects (role: node)
|
||||
When a node connects over the Gateway WebSocket with `role: node`, the Gateway
|
||||
upserts a presence entry for that node (same flow as other WS clients).
|
||||
|
||||
## Merge + dedupe rules (why `instanceId` matters)
|
||||
|
||||
Presence entries are stored in a single in‑memory map:
|
||||
|
||||
- Entries are keyed by a **presence key**.
|
||||
- The best key is a stable `instanceId` (from `connect.client.instanceId`) that survives restarts.
|
||||
- Keys are case‑insensitive.
|
||||
|
||||
If a client reconnects without a stable `instanceId`, it may show up as a
|
||||
**duplicate** row.
|
||||
|
||||
## TTL and bounded size
|
||||
|
||||
Presence is intentionally ephemeral:
|
||||
|
||||
- **TTL:** entries older than 5 minutes are pruned
|
||||
- **Max entries:** 200 (oldest dropped first)
|
||||
|
||||
This keeps the list fresh and avoids unbounded memory growth.
|
||||
|
||||
## Remote/tunnel caveat (loopback IPs)
|
||||
|
||||
When a client connects over an SSH tunnel / local port forward, the Gateway may
|
||||
see the remote address as `127.0.0.1`. To avoid overwriting a good client‑reported
|
||||
IP, loopback remote addresses are ignored.
|
||||
|
||||
## Consumers
|
||||
|
||||
### macOS Instances tab
|
||||
|
||||
The macOS app renders the output of `system-presence` and applies a small status
|
||||
indicator (Active/Idle/Stale) based on the age of the last update.
|
||||
|
||||
## Debugging tips
|
||||
|
||||
- To see the raw list, call `system-presence` against the Gateway.
|
||||
- If you see duplicates:
|
||||
- confirm clients send a stable `client.instanceId` in the handshake
|
||||
- confirm periodic beacons use the same `instanceId`
|
||||
- check whether the connection‑derived entry is missing `instanceId` (duplicates are expected)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user