Files
plyngent/doc/plyngent.example.toml
T
NCBM 0e8b0f8bd4 docs: describe deletes that go to the trash
A new page, `doc/trash.md`, covers what a delete does now: where a path lands on
each platform, what is still deleted outright (an explicit `permanent=true`, a
host that cleared `[trash]`, scratch paths under the OS temp dir, the
`copy_path` / `move_path` overwrite, the temporary-workspace cleanup), what
happens when a trashing is refused — the file stays and the error says how to
delete it on purpose — and the two things the trash cannot help with: a shell
`rm` and anything a plugin or MCP server does on its own.

`[trash]` joins the example config and the README snippet, the README gains the
one-paragraph version, `AGENTS.md` the tool/config notes (including the confirm
wording a delete now uses), and the default tool playbook tells the model that
`delete_path` is undoable and what `permanent=true` costs.
2026-10-21 20:25:27 +08:00

240 lines
12 KiB
TOML

# Example plyngent configuration (no real secrets).
# Copy to the path shown by: plyngent config path
# Then set access_key_or_token and adjust models.
# Optional. If omitted (or SQLite url unset/empty), the CLI uses a durable file under
# the platform user data dir (e.g. ~/.local/share/plyngent/chat.db).
# url = ":memory:" → true in-memory SQLite (CLI warns; useful for tests).
# [database]
# implementation = "sqlite"
# url = "/path/to/chat.db"
# # url = ":memory:"
# Third-party plugins (entry-point names). Default: load none.
# Today the CLI loads allowlisted ``plyngent.tools`` entry points; the section is
# not tool-specific so other extension points can share the same allowlist later.
# See doc/plugins.md.
# [plugins]
# enable = ["acme"]
# enable = ["*"]
# disable = ["legacy"]
# Host network / SSRF policy for tools such as ``fetch``.
# Clash TUN + Fake-IP often resolves public names to 198.18.0.0/15; list that
# pool so fetch does not treat every site as private/loopback.
# Metadata (169.254.169.254) stays forbidden even if listed.
# [networking]
# ssrf_assume_public_cidrs = ["198.18.0.0/15"]
# Skills: instruction directories holding a SKILL.md (YAML frontmatter with at
# least name/description, then a Markdown body) plus optional bundled files.
# Discovery roots in priority order: explicit `paths`, our own user config
# directory (<config dir>/skills), other harnesses' skill directories
# (~/.claude/skills; plus <workspace>/.claude/skills when include_project_roots).
# A session-start notice describes the tools; what exists is found with
# skill_list, bodies are read with skill_read, contents searched with skill_search.
# Reading needs no grant (the roots are read-only roots); creating or editing a
# skill asks once/session, or stands with allow_write = true. See doc/skills.md.
# [skills]
# enabled = true
# paths = ["~/team-skills", "/srv/skills"]
# discover = ["plyngent", "claude"]
# include_project_roots = true
# allow_write = false
# Notifications while a prompt waits on the human: a tool confirmation, a
# directory-access grant, a skill write, an ``ask_user_*`` question, or the
# limit-continue prompt. A prompt is announced only after `grace_seconds` of
# waiting, so one you answer at the terminal stays silent; `min_interval_seconds`
# throttles bursts.
# channels: "auto" tries the desktop first (needs the `notify` extra:
# `pip install plyngent[notify]`) and falls back to a terminal escape sequence;
# "desktop" / "terminal" pin one of them.
# `actions` offers allow / deny / review as notification buttons; where the
# platform has none the notification stays text-only.
# `include_reason` adds the one-line reason — it can name a command or a URL, so
# it stays off by default (a notification may be mirrored to a lock screen).
# [notify]
# enabled = true
# grace_seconds = 5.0
# min_interval_seconds = 10.0
# channels = ["auto"]
# actions = true
# include_reason = false
# protocol = "" # terminal protocol: "" = detect, or osc777 / osc9 / bell
# Deletes. `delete_path` moves the path into the desktop's trash (freedesktop
# trash / Finder trash / Recycle Bin) so it can be restored from the file
# manager; `permanent=true` on the call removes it for good, and paths under the
# system temp dir are always deleted outright. `enabled = false` restores the
# old "a delete is final" behaviour for every call. See doc/trash.md.
# [trash]
# enabled = true
# MCP (Model Context Protocol) servers exposed as agent tools.
# Defining a server opts it in; tools appear as mcp__<server>__<tool> with
# LOCAL tags (confirm flow applies). Names in disable stay disconnected.
# A server's initialize ``instructions`` (usage guidance) is folded into the
# agent system prompt when tools are on ([agent] mcp_instructions = false
# disables; /mcp previews what each server sent).
# [mcp.servers.docs]
# command = "npx"
# args = ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
# env = { RUST_LOG = "info" }
# cwd = "/path" # spawn working directory (empty = inherit)
# timeout = 30.0 # per-request timeout (seconds)
# read_only = true # mark every tool READ_ONLY (eligible for /btw --tools=read)
# [mcp]
# disable = ["docs"] # defined but never connected
[agent]
# Persona (omit → built-in coding-agent line). tool_directives is the tool playbook.
# Override with multi-line literals (prefer ''' so nested " is fine):
# system_prompt = '''
# Your custom persona...
# '''
# tool_directives = '''
# ### Workspace
# ...
# '''
# system_prompt = "" # persona off; keep default tool_directives unless also cleared
# tool_directives = "" # playbook off (set both "" to disable system entirely)
max_tool_result_chars = 32000
parallel_tools = true
confirm_destructive = true
path_denylist = ["/secrets/", ".ssh/", ".gnupg/"]
# Static out-of-workspace pre-allow (path → read|write|exec); never prompts.
# The model can also request access at runtime via request_directory_access
# (human approves; /grants lists or revokes). Denylist still applies, and
# grants are a tool-policy gate, not a sandbox (commands reach the FS).
# allow_paths = { "/data/datasets" = "read", "/tmp/build" = "exec" }
# Auto-raise tool/PTY limits without prompting (same as answering yyy).
# auto_continue_limits = false
# Soft context budget in estimated tokens (~4 chars/token when API usage missing).
max_context_tokens = 200000
# Fold connected MCP servers' initialize ``instructions`` (usage guidance)
# into the agent system prompt when tools are on. false = ignore server text.
# mcp_instructions = true
# How open-todo nags are injected into model context:
# developer (default) | user | synthetic_tool | none
# (legacy system is treated as developer)
# todo_nag_strategy = "developer"
# Append-only developer playbook checkpoints every N prompt_tokens (0 = off).
# directive_reminder_tokens = 100000
# directive_reminder_text = ""
# Thinking strength, resolved as [agent] < provider < model (the most specific
# layer wins, per field). "" = send nothing and keep the provider's own default.
# none | minimal | low | medium | high | xhigh | max
# ``max`` is DeepSeek's own top level on chat completions and clamps to ``xhigh``
# everywhere else. The agent maps the value onto each API surface: chat
# completions send ``reasoning_effort``, Responses send ``reasoning.effort``, and
# Anthropic — which has no effort level — sends ``thinking`` with the token
# budget the level maps onto (minimal 1024 … xhigh/max 32768, none = disabled).
# reasoning_effort = "medium"
# Explicit Anthropic thinking budget in tokens, overriding that mapping; the
# request stays valid either way (floor 1024, and ``max_tokens`` grows when the
# budget would leave no room for the answer). A model that only takes adaptive
# thinking ignores it (see the anthropic provider below).
# thinking_budget_tokens = 8000
# Optional: custom compact/summarisation prompts (empty = built-in defaults).
# compact_system_prompt = ""
# compact_user_prefix = "Summarize the following conversation:\n\n{transcript}"
# compact_seed_text = "Compacted from {src}:\n\n{summary}\n\nCarry on."
# --- OpenAI-compatible (vLLM, LiteLLM, OpenAI, …) ---
[providers.openai_compat]
preset = "openai-compatible"
url = "https://api.openai.com/v1"
access_key_or_token = "sk-replace-me"
# HTTP timeouts for LLM API calls (seconds). Default if omitted: connect=10, read=600.
# timeout = 120
# timeout = { connect = 10, read = 600 }
[providers.openai_compat.models]
# ``image_in`` is the opt-in for a model that reads pictures (JPEG, PNG, GIF,
# WebP); an ``@image`` mention is skipped without it. See doc/images.md.
"gpt-4o-mini" = { text = true, image_in = true, cost_factor = 1.0 }
# --- OpenAI platform (Responses API; tag is "openai") ---
# If ``preset`` is omitted, it defaults to "openai".
# If ``models`` is omitted, defaults: gpt-5.4, gpt-5.4-mini, gpt-5.4-nano.
# [providers.openai]
# preset = "openai"
# access_key_or_token = "sk-replace-me"
# url = "https://api.openai.com/v1"
# # Thinking strength for every model of this provider ("" = inherit [agent];
# # a model entry may override it). Values as in the [agent] section above.
# reasoning_effort = "medium"
# # Hosted/provider-side tools (OpenAI runs them; not local ToolRegistry).
# # Default if omitted: [{ type = "web_search" }]. Use [] to disable.
# # Examples: web_search, file_search (+ vector_store_ids), code_interpreter, mcp.
# provider_tools = [
# { type = "web_search" },
# # { type = "file_search", vector_store_ids = ["vs_xxx"] },
# # { type = "code_interpreter", container = { type = "auto" } },
# ]
#
# [providers.openai.models]
# "gpt-4o-mini" = { text = true }
# # Model-level preset/url overrides let one provider route models via different APIs.
# # `preset = "openai"` means /responses; `openai-compatible` means /chat/completions;
# # `anthropic` means native /messages.
# # "claude-sonnet" = { text = true, preset = "anthropic", url = "https://api.anthropic.com/v1" }
# # A model entry may also set its own thinking strength, e.g. skip thinking for
# # a cheap model: "gpt-5.4-nano" = { text = true, reasoning_effort = "none" }
# --- Anthropic (Messages API) ---
# [providers.anthropic]
# preset = "anthropic"
# access_key_or_token = "sk-ant-replace-me"
# # url defaults to https://api.anthropic.com/v1 when empty
# timeout = { connect = 10, read = 600 }
# # Anthropic has no effort level: an effort becomes a thinking token budget
# # (see the [agent] section above). A model that no longer takes one (from
# # Claude 4.7 on) is retried with adaptive thinking, whose depth is the effort
# # level itself, and a model that cannot stop thinking gets no thinking block
# # at all. Set the budget explicitly to pin it:
# reasoning_effort = "high"
# thinking_budget_tokens = 16000
#
# [providers.anthropic.models]
# "claude-sonnet-4-20250514" = { text = true }
# "claude-opus-4-20250514" = { text = true, reasoning_effort = "xhigh" }
# --- DeepSeek (OpenAI-compatible / Responses / Anthropic conventions) ---
# Default models if [providers.*.models] is omitted: deepseek-flash, deepseek-v4-pro.
# [providers.deepseek]
# preset = "deepseek"
# access_key_or_token = "sk-replace-me"
# # url defaults by convention: https://api.deepseek.com (chat/responses),
# # https://api.deepseek.com/anthropic (anthropic). Set explicitly to override.
# # Convention selects the API surface:
# # "" / "openai" → OpenAI-compatible chat completions (default)
# # "responses" → OpenAI Responses API (POST /responses; today only
# # deepseek-flash supports it)
# # "anthropic" → Anthropic Messages API (POST /messages on
# # https://api.deepseek.com/anthropic; claude-* model ids
# # map server-side to deepseek-v4-pro / deepseek-flash)
# convention = "responses"
# # Legacy location (parsed but ignored; prefer the typed field above):
# # extras = { convention = "openai" }
# # Thinking strength for this provider; per-model entries below may override it.
# # reasoning_effort = "high"
#
# # Local ``web_search`` sources, in fallback order (``[]`` disables the tool).
# # "deepseek" is the platform Search API (POST /v1/tools/search; billed per
# # successful request, 5 QPS). "bing" is a brittle RSS backstop — opt in with
# # ["deepseek", "bing"] when you want a fallback.
# # ``provider_tools`` controls only the hosted search, these sources control
# # plyngent's own tool: on the Anthropic convention ``provider_tools = []``
# # leaves you the local Search API tool instead of the hosted one.
# search_sources = ["deepseek"]
#
# [providers.deepseek.models]
# Defaults: "deepseek-flash" takes pictures (image_in = true), "deepseek-v4-pro"
# is text-only.
# "deepseek-flash" = { text = true, image_in = true, convention = "responses" }
# "deepseek-v4-pro" = { text = true, reasoning_effort = "max" }