OneBot v11
- Adapters
- SnowLuma recommended
- Connection
- The adapter dials Diana's reverse WebSocket
- Address
ws://HOST:18080/onebot/v11/ws- Features
- Group lists, CQ codes, quotes, group files, local media
Configure
Establish a working model connection first, then assign model roles and platform channels, and finally narrow triggers, hours, users and tool boundaries per group.
Platforms
Every enabled bot profile runs in parallel, and replies always return to the channel they came from. Credentials are entered per platform on the Bots page; leaving a secret field blank keeps the stored value, and filling it in replaces it.
What actually shapes deployment is not the platform itself but how messages reach you. The first four connect outbound from Diana, so a home connection or an internal network works as-is. Feishu and WeCom can only POST events to you, which means a publicly reachable HTTPS address.
| Platform | How it connects | Public address |
|---|---|---|
| OneBot v11 | Reverse WebSocket, the adapter connects to Diana | No |
| Telegram | Bot API long polling, outbound from Diana | No |
| QQ official bot | Open Platform WebSocket gateway, outbound from Diana | No |
| DingTalk | Stream mode connection, outbound from Diana | No |
| Feishu / Lark | Event subscription callback, pushed to Diana | Yes |
| WeCom | Custom-app callback, pushed to Diana | Yes |
| Tencent iLink Bot QR login and long polling, outbound from Diana | No | |
| iMessage | BlueBubbles Server on a Mac pushes webhooks; optional polling fallback | No, the Mac only needs to reach Diana |
ws://HOST:18080/onebot/v11/wsopen.larksuite.comilinkai.weixin.qq.com) long polling; the cursor and per-contact context_token live under weixin/ in the data directory, so a restart picks up where it left off/api/channels/imessage/callback?token=…Feishu and WeCom need the addresses below entered in their own consoles. HOST must be reachable from the platform's servers, over HTTPS.
https://HOST/api/channels/feishu/callback
https://HOST/api/channels/wecom/callback
With more than one bot on the same platform, append the profile id to tell them apart, for example /api/channels/feishu/callback/<profile-id>. With a single bot the short address is enough.
Platform servers cannot carry your session, so each is verified by its own scheme instead: WeCom checks msg_signature, decrypts with WXBizMsgCrypt and confirms the corp id inside the payload; Feishu checks the Verification Token and, when an Encrypt Key is set, also verifies X-Lark-Signature before decrypting. The callback URL itself is public information and is not a credential, so do fill in the verification secrets.
/media/resolver on OneBot; Telegram cannot reach a local address, so files are uploaded directly as multipart instead.Each bot keeps its own platform, account, owner, persona and model assignments. Replies always go back to the source channel. Conversation context is always isolated by bot profile; matching group or user IDs never merge sessions. Message relays remain available. Cross-platform memory is a separate opt-in on both bots and only retrieves non-sensitive public group facts and summaries, not raw conversations or personal memories.
The legacy isolate_platform_contexts setting is ignored and its endpoint has been removed. Previously shared history is retained in its original namespace, without automatic reassignment to individual bots. New messages use each bot's isolated sessions.
Models
A provider holds the base URL, API key and protocol. The top of the page syncs the model catalogue from the server and the bottom picks a default; the bot configuration then binds each role to a specific model.
| Role | Used for | How to test it |
|---|---|---|
| Chat | Final replies and agent reasoning | Check phrasing, tool calls and long context. |
| Vision | Photos, screenshots, historical images | Send a real image and check small-text OCR, UI structure and multi-image relations. |
| Intent | Unprompted replies, suppression, relationship checks | Check latency, stable structured output and edge cases. |
| Image generation | Text-to-image and image editing | It must actually return an image — a text-only connectivity check is not enough. |
"Detect other bots and stay quiet" is enabled by default to stop bots talking to each other in a loop. Turn it off only when you have a concrete reason.
A profile's "Credential" setting can use OAuth instead of an API key. The console usually runs on a server and your browser is not necessarily on the same machine, so the callback does not have to land back locally: click "Sign in", finish the authorization in your own browser, then paste the whole callback URL from the address bar back in (just the code also works). The token is refreshed before it expires; if the profile also holds an API key, a failed refresh falls back to it rather than taking the whole profile down.
Providers are configuration, not code. OpenRouter ships built in; anything else goes under "Custom provider" with its authorize URL, token URL, client ID and scopes — suitable for a self-hosted gateway or any service not shipped here. Both URLs must be https; loopback addresses (127.0.0.1) may use http.
How the token is then presented to the model API is configuration too. The default Authorization: Bearer <token> suits almost every provider; not all of them, though — Anthropic answers an OAuth token in Authorization with a 401 "OAuth authentication is currently not supported." and wants the token in x-api-key with no prefix. So a custom provider can set the token header, the token prefix and a set of extra headers under "Advanced". When the token goes somewhere other than the default, the auth header the SDK writes itself is stripped so one request never carries two credentials.
The request body format of the token exchange is chosen under "Advanced" too. RFC 6749 §4.1.3 requires application/x-www-form-urlencoded at the token endpoint, so that is the default; JSON is a dialect some services speak instead, and the built-in OpenRouter only accepts JSON. Send the wrong one and the server usually answers with a bare "missing parameter" rather than saying the whole body failed to parse, which sends you looking at the client ID. Custom providers saved before this option existed keep sending JSON so a working setup does not suddenly stop exchanging tokens; newly added ones use the spec default.
That use misrepresents the client's identity to the authorization server and goes beyond what a consumer subscription itself licenses. Whether to do it is the operator's own call — it can be entered under "Custom provider".
llm:
context_window_tokens: 65536
max_context_tokens: 65536
max_output_tokens: 2048
timeout: 60000ms
Images consume vision tokens too. When vision quality is poor, first confirm that the role really is bound to a vision model, that the original image made it into the request, and that images were not dropped by count or token budget — before raising the detail level or the context ceiling.
Group policy
Group management syncs every group the bot has joined through the OneBot get_group_list call, not just the ones with local configuration. It falls back to the groups stored in SQLite only when the connection is unavailable.
Private chats are disabled by default, including ordinary messages and private notices from the owner. Enable all users, owner only or a whitelist under Bot → Behavior → Admission and limits when needed. Disabled messages are silently ignored without queueing, preprocessing or model calls; group chats and console login pairing are unaffected. Explicitly saved admission modes remain effective; missing or invalid modes disable private chats.
Stopping one group does not affect other groups or direct messages.
Explicit mentions, quotes, trigger words and semantic routing are evaluated in layers; an explicit mention outranks a quote.
Same-day and overnight windows with IANA time zones. This is group policy and never enters the model prompt.
After reserving output, safety overhead and other media, estimated text and image tokens each receive half the remaining budget and may borrow unused capacity. Requests that fit are unchanged. Text overages are first handled without a model call: the oldest chat history is dropped turn by turn (replaced by a short omission note), then older large Agent tool results are clipped to their head and tail, down to 85% of the text limit so the next few tool results still fit. Later Agent steps of the same reply reuse the same cut point, keeping the prompt prefix stable. System rules, the current question, the last three turns, memory and summaries, the two newest tool results and tool-call pairing are never touched. Only if the request still does not fit are the longest older messages summarized by a model, at most once per text per reply. Only image overages trigger numbered visual descriptions, which then count toward text usage. Failed descriptions retain the originals; image detail may be reduced only for image overages. If the request still cannot fit, images are dropped first and the remainder is left to the provider's context trim instead of failing the turn. Logged token counts are estimates, not provider billing usage.
Disabling multiple messages joins the reply into one message, compresses oversized content and disables automatic merged forwarding. Paragraph line breaks are configured independently: an unset value preserves existing behavior, while disabling them compacts ordinary prose without damaging lists, code, tables or quotations. Both options support group inheritance and explicit current-turn overrides without persisting those overrides. Models continue to use explicit message and line markers; raw newlines do not control layout.
Blacklist (default) replies to everyone but the muted accounts; whitelist replies only to the listed accounts and sends nothing at all to the rest, not even the quiet-hours notice. The owner is exempt from the whitelist.
The group list and the global list are a union: adding someone here never drops the global mute list. Level and hours, by contrast, are taken over wholesale by "set reply rules for this group".
Group-level prompts, history depth and per-message reply limits can override bot defaults. The existing max_reply_chars field and default of 3500 remain, but now apply per message on every platform, not to the combined length of all parts. Generation prompts include the limit. When splitting is allowed, complete paragraphs, sentences or sections are grouped first; only parts that remain oversized are compressed, leaving other parts unchanged. At most two compression calls are allowed per reply. Failure sends no partial result, and content is never hard-truncated. An explicit single-message request instead compresses the whole reply. Model output token budgets and delivery pacing still apply.
Plugin settings and credentials are shared globally, with no sharing-mode switch. Each bot retains its own enabled state. Saving or resetting plugin settings affects all bots. Migration selects the first complete configured value set in saved bot order, keeps the other configurations as backups, and shows the source bot. Credentials from different accounts are never combined. Existing non-secret group overrides remain supported. RSS and GitHub subscriptions support multiple destinations, each selecting its own bot and group or private chat. Each subscription is checked once before delivery to its destinations; existing subscriptions are not automatically merged.
QQ/OneBot merge thresholds package the parts into a forward card without shortening the content. Telegram ignores those thresholds and never automatically concatenates separate messages. It sends normal parts or one message according to the user's choice and validates rendered platform capacity. Oversized parts are compressed first; content such as code that cannot be safely reduced still fails rather than being corrupted. Other platforms never attempt OneBot forwarding.
Persona
Each bot's persona is a single Markdown document: who she is, what she cares about, how she talks, what she looks like. It is placed verbatim at the very front of the system prompt, with no wrapper. Edit it under Bots → Persona; a group can replace it wholesale in group management, or leave it empty to follow the bot.
Diana's way of writing it is to give reasons, not rules: rules always miss cases nobody foresaw, while a character who understands why can work out situations the document never mentions. Only the few things that are costly to get wrong become hard lines. Write about her in the owner's voice ("we"), say that her identity holds under pressure, and describe her appearance. Output format, message splitting, tools, time and which group she is in need not be written — the runtime adds those. See docs/soul-md.md in the repository for the full guide.
The library is a source to apply from, not a live binding: picking one fills its text into the editor, and it takes effect when you save the bot. Editing the library entry later never silently changes a bot you already configured.
name.md; importing a .md takes the name from the first-line heading, or from the file name.Persona files are .md only; older JSON / YAML persona files no longer import. Nothing already stored is lost: on upgrade, the soul layer, self-reference, sentence-ending particles and action-description switch in bot configurations and in the library are rendered into the same text the runtime used to inject and folded into SOUL.md, so the model reads the same instructions — they are just visible and editable now. Prompt overrides and criteria that older library personas carried no longer travel with a persona and are dropped; each bot's own prompt overrides and criteria are kept as they are.
Import also accepts SillyTavern character cards: V1/V2/V3 JSON files and PNGs with an embedded card (the chara/ccv3 tEXt chunk). The card's description, personality, scenario, example dialogue and greeting are composed into one document ({{char}}/{{user}} macros are expanded in place; past the 4000-character budget, later sections are dropped whole). An embedded character_book is merged into the world book along the way.
Reply mode (how readily it joins a chat), refusal wording and model assignments are not part of the persona: they describe how this bot runs in a group, not who she is.
World and interaction
The world book is the lore book of the world your bot lives in, maintained under Bots → Persona → World book. Entries are organised as chapters on a tree, and the path itself becomes context ("Zhijiang / Harbor: …"). Each node declares how it is injected: always-on entries ride along on every turn and describe the skeleton of the world; entries with trigger keywords only enter the context when recent conversation touches them, so details never waste tokens. Title-only nodes act as folders and inject nothing; disabling a chapter disables everything under it.
There is one book shared by all bots; each bot's own switch decides whether it uses it (on by default — an empty book injects nothing anyway). The whole book exports to JSON and imports back; parent-child links are rebuilt from the references inside the file, and like persona files, IDs are machine-local state and are reassigned on import.
Import is also compatible with SillyTavern world books: a world info file's top-level entries, or the character_book embedded in a character card, can be selected directly. Fields map by meaning — blue-light constant becomes always-on, green-light keys become trigger keywords, comment becomes the title (falling back to the first key or the start of the content), disable becomes disabled — sorted by order and placed at the root. Secondary keys (keysecondary, AND ANY logic) carry over too: the entry then requires a primary hit plus any secondary hit. Other secondary logics (NOT ANY etc.) invert the meaning, so those entries fall back to primary keys only. Recursive scanning, probability and insertion position have no counterpart here and are ignored; injection follows the local rules.
Bots → Behavior → Human-like adds three independent switches, all off by default:
Agent
The agent runner feeds structured tool results back to the model until it produces a final reply or hits the step limit. The tool registry narrows by platform, relationship permissions, group configuration and feature state.
Web search ships with Diana and cannot be installed or removed. The model is told to search first for time-sensitive questions, fact checks, unfamiliar entities and recommendations.
One-off and recurring tasks share the same persistence; the console shows the schedule, next run, source and status.
When someone in a group asks to be sent something privately, cross_session_message delivers the full text into the direct chat right away; the owner can also use it to post into a named group. The destination can never be the current conversation, so it cannot stand in for an ordinary reply. It defaults to the person speaking; only the owner may name someone else or a group. Users outside private admission, blocked users and groups that are not admitted or are switched off get nothing; the same destination is never sent two in a row, and one originating session may send at most five in ten minutes. On QQ the friend roster decides between an ordinary direct message and a temporary session through the shared group; when neither works the text is parked and delivered automatically once the person adds the bot, expiring after seven days with at most three parked per person. The friend request itself still needs the owner's approval: having something to deliver never lets the bot admit someone on the owner's behalf.
External extensions are discovered from configuration and constrained by working directory, allow lists, permissions and timeouts.
Recognition, generation and editing are authorised separately; image generation is health-checked with a real generation request.
host_stats lets the bot answer "how much memory is in use", "is the CPU busy", "how hot is it right now": CPU model and load, load average, memory, disk, Diana's own usage, plus whatever temperature, power and battery readings the machine exposes. Read-only, no parameters, and it shares its collector with the console dashboard.
Temperatures come from /sys/class/hwmon, power from the RAPL energy counters, battery from /sys/class/power_supply — no dependency on lm-sensors or any other external command, and a container only needs /sys mounted. RAPL exposes cumulative energy rather than instantaneous power, so power needs the difference between two samples; the first call says so instead of inventing a number. On macOS powermetrics needs root and Windows has no general interface, so both report temperature and power as unavailable — a fabricated number is worse than no number. That holds for every field: anything that cannot be read is reported with the reason, never as a zero.
This tool is owner-only too: hostname, disk paths and hardware models should not be visible to everyone in a group.
Every local tool is confined to workspace under the data directory. The path is fixed and not configurable; absolute paths and ../ are rejected. The three tiers are independent:
| Tier | Tools | Default | Switch |
|---|---|---|---|
| Read | read_file, list_files, grep, find_files, manage_files stat | on | follows the "built-in agent" switch |
| Write | write_file, edit_file, save_to_workspace, manage_files move / copy / delete / mkdir | on for new profiles | "allow file writes" |
| Execute | run_command | on for the owner when the sandbox works | available to the owner and WebUI admin sessions in standard mode; registered only when the command sandbox is effective, always offline |
A newly created bot works out of the box: writes are on (confined to workspace under the data directory, so config and the database stay out of reach). run_command has no program allowlist, but it is registered only when the command sandbox actually works: commands stay in the workspace, cannot read credential files, are always offline, and are not reviewed before running. Without a usable sandbox, or with the sandbox set to off, the tool is not registered and the owner's commands go through host_access, which a model reviews before every execution; commands that need the network go there too. The rule is one sentence: anything that can change the machine or send data out gets reviewed. Member sessions and safe mode never register the tool.
"Allow plugin scripts and package installs to use the network" only affects install_package and the scripts and MCP servers a plugin declares. Their entry points are fixed at install time; they follow the sandbox setting and, without a sandbox, run with Diana's own process permissions, which the startup log reports.
The agent_command_allowlist key is ignored on load and no longer written on save, so an empty allowlist no longer turns run_command off. The owner can already run any host command through host_access, which made the allowlist a duplicate restriction. To stop local execution entirely, switch to safe mode. The file-write default still applies to new profiles only: a write switch left off stays off after an upgrade, and "Fill in recommended defaults" on the same form only edits the form until you press Save.
write_file only writes text and refuses binary targets such as .png, .pdf, .mp4 or .zip; read_file likewise refuses binary files and points to view_image for pictures and manage_files stat for size and type. Images, audio, video, PDFs and archives go in through save_to_workspace, byte for byte: from a message in the current conversation (including images the bot itself sent), from a public URL (through the same private-address-blocking download cache as other fetches), or from an MCP media_id. The real type is sniffed from the bytes and the extension corrected (JPEG is always saved as .jpg). Without an explicit path, chat media and URLs land in downloads/ and the bot's own output in outputs/; existing files are never overwritten by default. When the owner has file writes on, images from image are still delivered as before and the original is also saved to outputs/. manage_files delete does not delete: it moves the entry into .trash/<timestamp>/, keeping its relative path; entries inside the trash cannot be moved or deleted again with manage_files, and nothing can be saved into it.
The workspace is split by purpose: tmp/ for drafts and intermediate files (cleaned after 1 day), downloads/ for downloads and files saved from chat and .agent-browser/ for screenshots (7 days), outputs/ for the bot's own output (30 days), and .trash/, purged 7 days after deletion, all by the daily storage maintenance. keep/<bot>/ is the long-term area and is never cleaned automatically: when the owner says to keep something, save_to_workspace is called with keep=true (or the file is moved into keep/ with manage_files) together with a short description. Each bot's long-term area is capped at 2 GB; beyond that saves are refused rather than evicting older files, and other bots' long-term areas are read-only. The index (path, description, source message, who saved it, when, size, type) lives in .diana/keep-index/ and the newest 20 entries are added to the tail of the owner's reply prompt. Loose files in the workspace root and coding/ repositories that no bot references and nobody touched for 30 days are only reported (log and WebUI), never deleted. Settings → System → Workspace lists files by area and lets you download, delete (to the trash) and empty the trash.
Each edit_file replacement must match exactly one place in the original file; several matches or none rejects the whole batch rather than editing the wrong spot. All replacements are located against the original file, not applied one after another — applying them in sequence would let a later replacement match what an earlier one just wrote, and that edit is invisible in the caller's input.
These tiers are owner-only, but "the call happened in the owner's conversation" is not "the owner asked for this call": other people's messages share the context with the owner's, and the model decides to call a tool after reading them. The real boundary is these switches and the allowlist, not the identity check — open the tier you need and leave the rest closed.
Coding agent
The coding agent plugin (official.coding-agent) lets the bot hand a coding job to a coding CLI installed on the host: edit code, run tests and commit inside a persistent workspace, for minutes or for hours. You can ask for progress at any point, and when the run ends the bot reports back to the conversation that assigned it. Disabled by default — it changes code in the background and speaks up on its own, so enabling it is a decision you make, not one made for you.
The tool is coding, owner-only, with seven operations: submit, status, tail (the last few actions), cancel, followup (continue in the same CLI session), list and workspaces.
When a job is still running a few seconds after dispatch, Diana itself posts a fixed-format acknowledgement with the job ID, agent, workspace and a summary of the instruction; the model does not write it. That message is the proof a job was really dispatched: if the model claims it handed work to Claude Code without calling the tool, no acknowledgement appears. Jobs that end within those seconds (an auth failure, a suspended account) get no acknowledgement; the dispatching turn replies with the result directly, and when that result is longer than 1,500 characters Diana posts the full text first so the model only states the conclusion. The final report starts with a header that mentions the requester. Results up to 1,500 characters follow in the same message; for longer reports the header carries only the opening lines and the full text follows as one merged-forward card (or as split messages when the bot has merged forwards off or the platform lacks them; a forward that gets no acknowledgement is treated as delivered rather than sent again). If the full text fails, the retry sends only the full text, not the header again. The job record keeps the full result, capped at 20,000 characters; status gives the model only the first 1,500 and sets result_truncated.
| Setting | What it does |
|---|---|
| Backend CLI | claude / codex / acp / custom command. Claude Code's stream-json yields an exact session ID, step-by-step progress and a structured result (model turns and cost included). acp connects ACP agents such as Gemini CLI or opencode: the executable is the agent command and the command template holds only the agent's own arguments (for example --acp); it gives structured progress and a final conclusion. A custom command falls back to reporting the tail of the log. |
| Executable | Empty means the backend's default name, resolved on PATH. Give an absolute path when the CLI is not on PATH. |
| Command template | Empty uses the backend preset. Placeholders {{instruction}}, {{model}}, {{session}}, {{workspace}}; a placeholder with no value drops its argument along with the flag right before it, so an unset model never leaves a dangling --model. Arguments never go through a shell — quotes and semicolons in an instruction are just characters. |
| Workspace allowlist | One name=local path or name=git URL per line. Only workspaces registered here can be assigned work. A git URL is cloned to workspace/coding/<name> on first use and stays there. |
| Max runtime per run | 120 minutes by default, 1440 at most. On timeout the whole process group is killed; finished changes stay in the workspace. |
| Concurrent jobs | Global limit. One job per workspace, always — not configurable: two agents editing one checkout overwrite each other. |
| API key | Injected as ANTHROPIC_API_KEY / OPENAI_API_KEY per backend. Left empty, the CLI's own stored credentials are used — note those belong to the system user running Diana, not to your laptop. |
The CLI starts in its own session (setsid), detached from Diana's process group, with output written to workspace/coding/.jobs/<job>.log and the job record as JSON beside it. On startup Diana scans that directory: a live process is picked back up, a finished one is concluded from its log and any owed report is delivered. The reported flag is only stored after the report goes through, so a failed delivery is retried on the next start rather than silently dropping an hour of work.
PIDs get reused, so re-attaching does not trust a live PID alone: a log quiet for more than 30 minutes ends the job as interrupted. Windows has no setsid equivalent, so jobs there do not survive a restart and are concluded as interrupted.
The coding CLI does not go through the built-in agent's command sandbox: it needs network access and repo-wide writes. The presets use --permission-mode auto, so routine changes go ahead on their own and anything that needs a nod goes through chat approval below. The real boundary is four things: only the owner can call it (the tool is absent from the non-owner allowlist and re-checks identity itself), work happens only in registered workspaces (workspace names reject path separators and ..), dangerous operations need the owner to reply with a code, and every call is written to the operation log.
A job runs unattended, but "and then push it" deserves a human nod. Diana installs a PreToolUse hook for the coding CLI: before each command the hook asks whether it may proceed. Anything not on the dangerous list goes straight through; a match stops the job and Diana sends the owner this:
编码任务 code-3f2a1b(工作区 diana)要执行一个需要你点头的操作:
Bash:git push origin main
同意就回 7c1d9e,不同意回 b40f22。10 分钟内没人回我就当拒绝。
The owner types the code in chat and the job continues; the deny code or a timeout blocks the step, and the CLI decides whether to work around it or end with an error. status reports awaiting_approval along with both codes, so a message that scrolled past can be asked for again.
Why a code rather than the word "yes". Same reasoning as the extension-change confirmation code: the decision reduces to one structural match with no semantic inference, so no phrasing can throw it off in either direction. More importantly the code appears only in the owner's own message — page text, tool output or someone else's message can say "approved" as often as it likes and nothing is released. The code must appear as a standalone fragment, so one embedded in a longer hash does not count; it is consumed on use, so replaying the same message a second time does nothing.
The hook process and Diana talk through a file mailbox under the job directory (.jobs/approvals/) — no local port, no token: the hook is a child of the CLI, its environment is not Diana's to decide, and that directory is always reachable. A useful side effect is that a Diana restart mid-wait loses nothing — the request file is still there, the hook is still polling, and the question is asked again after startup. The hook calls back into Diana's own executable (internal subcommand __coding-approve) rather than a generated script, which avoids executable bits and interpreter paths differing per platform.
| Setting | What it does |
|---|---|
| Chat approval | "Dangerous operations" (default) stops only on list matches; "all writes" asks once per edited file, which is noisy and mostly for trying things out; "off" runs unattended throughout. |
| Dangerous operation list | One command fragment per line, case-insensitive, a match requires approval. Defaults cover git push, git reset --hard, git clean -f, git tag -d, gh pr merge, gh pr create, gh release, npm publish, docker push, rm -rf, sudo. What earns a place: it cannot be taken back, or its effect reaches outside the workspace. Editing files, running tests and committing locally are not on it — those are implied by assigning the work. |
| Approval timeout | 10 minutes by default, then treated as a refusal. The job is stopped while waiting and that time counts against the max runtime, so raise both together. |
This works with Claude Code and ACP agents. Claude Code goes through the PreToolUse hook; an ACP agent goes through the permission requests it sends itself, using the same mailbox and codes, so only operations the agent chooses to ask about can be approved. Codex and a custom command have no equivalent. With approval on and a backend that cannot do it, submitting fails outright instead of quietly allowing everything: silently not asking would be a lie.
auto is fixed by adding it to the dangerous listThe hook stays silent on anything off the list, leaving those to --permission-mode auto. In non-interactive mode nobody can answer the CLI's own prompt, so a step auto wants confirmed is simply refused, and the job log names it. When that happens, add the command fragment to the dangerous list: a match goes through chat approval, and once the owner replies the hook returns an explicit allow, which overrides auto's hesitation. To skip adding them one by one, switch the template to bypassPermissions — note Claude Code rejects that mode when running as root, and a Docker deployment is root by default.
When the approval path itself breaks (an unreadable policy file, say) the hook allows and the reason goes to Diana's operation log. That is deliberate: Diana's own plumbing failing should not strand a job halfway. What you truly do not want touched is kept out by the workspace allowlist, not by approval.
As with the three local file and command tiers: "the call happened in the owner's conversation" is not "the owner asked for this call". Other people's messages share that context. The workspace allowlist is the gate that actually holds — register only repositories you are willing to let it change freely, and keep production config or secret repos out. To withhold push, switch the template to --permission-mode acceptEdits and block pushes with a git hook on those repositories.
The coding CLI cannot see this chat. When a change depends on what was said, the model puts the relevant quotes in the context parameter and they are prepended as background. Instructions cap at 8000 characters — they land in argv, which the OS limits per argument; longer material belongs in a file inside the workspace for the CLI to read itself.
Once a job finishes with a session ID, followup continues in that same session, so the CLI remembers the previous round. A running job takes no additions: a non-interactive coding CLI has no channel for mid-run input, and forcing one would just be a second process editing the same checkout.
Security
config.yaml, model API keys, bot tokens and platform cookies out of Git.