70 Commits

Author SHA1 Message Date
voidborne-d
bf8a6e35fd docs(deployment): match docker run gateway example to docker-compose.yml (refs #3873)
The `docker run` example for `gateway` in `docs/deployment.md` had drifted from
the canonical configuration in `docker-compose.yml`:

- It omitted the security flags that `docker-compose.yml` already declares
  (`cap_drop: ALL` + `cap_add: SYS_ADMIN` + unconfined apparmor/seccomp).
  These are required whenever `tools.exec.sandbox: "bwrap"` is enabled, because
  bwrap needs CAP_SYS_ADMIN for user namespaces; without them bwrap exits with
  `clone3: Operation not permitted` and exec tools silently fail.
- It omitted `-p 8765:8765`, even though both the bundled `docker-compose.yml`
  and `Dockerfile` (`EXPOSE 18790 8765`) already expose the WebSocket channel
  / WebUI port; users following the docs would get a reachable gateway health
  endpoint but an unreachable WebUI.

This change keeps the two paths in sync so anyone reading deployment.md and
using `docker run` directly gets the same security posture and port surface
as the Compose path.

Also adds a short `!IMPORTANT` note documenting that `gateway.host` and
`channels.websocket.host` default to `127.0.0.1` (set in
`nanobot/config/schema.py:GatewayConfig`). Docker `-p` cannot forward to the
container's loopback interface, so the user must set both binds to `0.0.0.0`
in `config.json` for the published ports to actually be reachable. This is
the symptom reported as items 2 + 3 of #3873; items 1 + 4 of that issue are
already resolved on `main` (`Dockerfile` line 49 already exposes both ports,
and README.md lines 218-220 already reflect that the WebUI ships in the wheel).

Docs only, no code changes.

Signed-off-by: voidborne-d <258577966+voidborne-d@users.noreply.github.com>
2026-05-18 00:45:49 +08:00
Xubin Ren
f017e209da docs(configuration): align Docker env-file example 2026-05-18 00:45:34 +08:00
olgagaga
5a34504b76 docs(configuration): expand "Environment Variables for Secrets" section
- Note that any string field supports ${VAR_NAME} and resolved values are
  never written back to disk.
- Document the failure mode for unset variables.
- Add MCP (stdio env + HTTP headers) and web-search examples.
- Add Docker, direnv, and secret-manager (1Password / pass / Bitwarden)
  delivery patterns alongside the existing systemd example.
- Replace plaintext apiKey values in tools.web.search examples (Brave,
  Tavily, Jina, Kagi, Olostep) with ${PROVIDER_API_KEY} placeholders so
  the docs stop modelling the anti-pattern.
- Cross-link from the Security section.

Refs: HKUDS/nanobot#2172
2026-05-18 00:45:34 +08:00
Xubin Ren
c018c3fb6a chore(release): bundle webui into wheel and prep 0.2.0 2026-05-16 13:38:11 +00:00
yanalialiuk
18072856ec feat: add Atomic Chat as OpenAI-compatible local provider
Register atomic_chat in the provider registry with default base URL
http://localhost:1337/v1, schema field, docs, and config tests.
2026-05-16 12:14:33 +08:00
chengyongru
2d64aa7dd8 docs(pairing): consolidate access control docs — MECE allowFrom + pairing 2026-05-15 15:46:44 +08:00
chengyongru
8aff3d6151 docs(pairing): add user-friendly pairing documentation 2026-05-15 15:46:44 +08:00
Xubin Ren
5efd67919b feat(runner): support fallback candidates
Resolve fallbackModels as preset references or explicit inline provider configs so failover uses complete model settings without exposing fallback logic to the agent loop.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 15:34:03 +00:00
Xubin Ren
43db848db0 Revert "feat(runner): support structured fallback models"
This reverts commit 02b059a616dc6dc82ad15282102c7b27a5a34e40.
2026-05-13 14:11:08 +00:00
Xubin Ren
02b059a616 feat(runner): support structured fallback models
Bind fallback model chains to the active model configuration so defaults and presets do not inherit or merge fallback behavior implicitly. Require explicit fallback providers while preserving per-fallback generation overrides and context-window safety.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 13:57:30 +00:00
Xubin Ren
9d50f1b933 feat: polish trace delivery and slash menu UX
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 08:47:34 +00:00
Xubin Ren
458b4ba235 feat(reasoning): stream reasoning content as a first-class channel
Reasoning now flows as its own stream — symmetric to the answer's
``delta`` / ``stream_end`` pair — instead of being shipped as one
oversized progress message. This lets WebUI render a live "Thinking…"
bubble that updates in place, then auto-collapses when the stream
closes. Other channels remain plugin no-ops by default.

## Protocol

New metadata: ``_reasoning_delta`` (chunk) and ``_reasoning_end``
(close marker). ChannelManager routes both to the dedicated plugin
hooks below; the legacy one-shot ``_reasoning`` is kept for back-compat
and BaseChannel expands it into a single delta + end pair so plugins
only ever implement the streaming primitives.

WebSocket emits two new events:

- ``reasoning_delta`` (event, chat_id, text, optional stream_id)
- ``reasoning_end`` (event, chat_id, optional stream_id)

## BaseChannel surface

- ``send_reasoning_delta(chat_id, delta, metadata)`` — no-op default
- ``send_reasoning_end(chat_id, metadata)`` — no-op default
- ``send_reasoning(msg)`` — back-compat wrapper, base impl forwards
  to the streaming primitives

A channel adds reasoning support by overriding the two streaming
primitives. Telegram / Slack / Discord / Feishu / WeChat / Matrix keep
the base no-ops until their bubble UIs are adapted; reasoning silently
drops at dispatch, never as a stray text message.

## AgentHook

Adds ``emit_reasoning_end`` to the hook lifecycle. ``_LoopHook`` tracks
whether a reasoning segment is open and closes it on:

- the first answer delta arriving (so the UI locks the bubble before
  the answer renders below),
- ``on_stream_end``,
- one-shot ``reasoning_content`` / ``thinking_blocks`` after a single
  non-streaming response.

## WebUI

- ``UIMessage.reasoning`` is now a single accumulated string with a
  companion ``reasoningStreaming`` flag.
- ``useNanobotStream`` consumes ``reasoning_delta`` / ``reasoning_end``;
  legacy ``kind: "reasoning"`` is auto-translated to a delta + end.
- New ``ReasoningBubble``: shimmer header + auto-expanded while
  streaming, collapses to a clickable "Thinking" pill once closed,
  respects ``prefers-reduced-motion``.
- Answer deltas adopt the reasoning placeholder so the bubble and the
  answer share one assistant row.

## Tests

- ``tests/channels/test_channel_manager_reasoning.py`` — manager routes
  delta + end, drops on channel opt-out, expands one-shot back-compat.
- ``tests/channels/test_websocket_channel.py`` — new ``reasoning_delta``
  / ``reasoning_end`` frames, empty-chunk safety, no-subscriber safety,
  back-compat expansion.
- ``tests/agent/test_runner_reasoning.py`` — runner closes the segment
  on streaming answer start and after one-shot reasoning.
- WebUI ``useNanobotStream`` + ``message-bubble`` cover the new
  protocol and the shimmer styling.

## Docs

``docs/configuration.md`` and ``docs/websocket.md`` document the new
events and the plugin contract.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 07:13:43 +00:00
Xubin Ren
a6b059d379 refactor(reasoning): make channel plugins own reasoning rendering
Reasoning was being shipped to every channel as a generic progress
message with a `_reasoning: true` flag. Two problems with that:

1. Channels without a low-emphasis UI primitive (Telegram, Slack,
   Discord, Feishu...) would dump raw model thoughts as ordinary
   replies, polluting the conversation.
2. The agent loop double-gated by inspecting `channels_config`, which
   coupled the loop to display policy.

Treat reasoning as its own plugin action — `BaseChannel.send_reasoning`
defaults to a documented no-op; channels that have a fitting affordance
override. ChannelManager routes `_reasoning` outbounds to that method
only when the channel opts in via `show_reasoning` (camelCase alias
`showReasoning` mirrors `sendProgress`). Plugins that don't override
silently drop reasoning — "no fit, no leak" is the contract.

Reference implementation lands for WebSocket / WebUI: a new
`kind: "reasoning"` frame, parked on the active assistant bubble as a
collapsible `Thinking` group above the answer. CLI keeps its existing
direct path (it doesn't go through the bus). `ChannelsConfig.show_reasoning`
flips to `true` by default — only adapted channels surface anything,
others stay quiet.

Loop net diff is -3 lines: the `channels_config.show_reasoning` check
moves out, leaving emit_reasoning a one-liner that publishes and trusts
the channel to decide.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 06:27:53 +00:00
Xubin Ren
01fa362c03 Merge origin/main into feat/show-reasoning
Resolves conflicts after main landed the state-machine turn refactor
and the test_runner.py 9-file split:

- nanobot/agent/loop.py: take main's `_state_build`/`_persist_user_message_early`
  flow; restore the `reasoning: bool` parameter on `_build_bus_progress_callback`
  so the loop hook can mark progress as reasoning-channel without coupling to
  the answer stream.
- nanobot/cli/stream.py: keep main's configurable `bot_name`/`bot_icon` header
  while preserving the PR's `transient=True` Live + `self._console` routing
  + `_renderable()` final-render path that fixed TUI duplication.
- tests/agent/test_runner.py was deleted on main and split into 9 focused
  files; relocated all 6 reasoning tests into a new `test_runner_reasoning.py`
  matching the new layout, deduplicated the per-test `ReasoningHook` boilerplate
  through a shared `_RecordingHook` helper.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 05:07:14 +00:00
Xubin Ren
352aaf0627 refactor(reasoning): unify reasoning extraction across providers
Reasoning surfacing was split across three branches in runner.py plus
two separate streaming buffers (loop hook and runner progress stream),
with three independent display-side gates in the CLI. This collapsed
the policy into one source of truth and fixed two real bugs:

- Structured `reasoning_content` was suppressed whenever the answer was
  streamed, because the runner gated emission on `streamed_content`.
  Providers don't stream `reasoning_content`; it only arrives on the
  final response, so the answer stream and the reasoning channel are
  independent. Added `streamed_reasoning` to `AgentHookContext` to track
  the right bit.
- `channels.showReasoning` was subordinated to `sendProgress`. They are
  orthogonal — turning off progress streaming shouldn't silence
  reasoning. Reworked the CLI gates accordingly.

Single-helper consolidation:

- `extract_reasoning(reasoning_content, thinking_blocks, content)`
  returns `(reasoning_text, cleaned_content)` with a defined fallback
  order: dedicated field → Anthropic thinking_blocks → inline
  `<think>`/`<thought>` tags. Models that expose none of these
  short-circuit to `(None, content)` — zero overhead.
- `IncrementalThinkExtractor` replaces the ad-hoc `emit_incremental_think`
  function and its hand-rolled "emitted cursor" state in both the loop
  hook and the runner progress stream.

Also documented the new `showReasoning` channel option in
docs/configuration.md and noted its independence from sendProgress.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 17:14:19 +00:00
Xubin Ren
35f64cd828 docs(config): document model presets
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 20:06:22 +08:00
chengyongru
49f85f5c23 docs(schema,config): clarify reasoning_effort semantics for MiMo thinking mode
- Update AgentDefaults.reasoning_effort comment to document "none"
  (disable) and None (preserve provider default).
- Add configuration.md tip explaining MiMo thinking mode behavior.
2026-05-11 14:38:28 +08:00
Xubin Ren
e936ed48bd feat: add image generation tool and WebUI mode
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 20:06:23 +08:00
Tim O'Brien
67875d7a15 fix: wire toolHintMaxLength through AgentLoop constructors
The config field was added but never passed from config to AgentLoop.
The value was always falling back to the default (40) regardless of
what was set in config.json.

Now passes tool_hint_max_length through all AgentLoop() call sites:
- nanobot/nanobot.py (main bot)
- nanobot/cli/commands.py (CLI agent, dev, webui commands)

Also adds documentation in docs/configuration.md.
2026-05-06 21:18:39 +08:00
chengyongru
c30e4d86f3 refactor(agent): simplify subagent concurrency with rejection over semaphore
Replace the asyncio.Semaphore queueing approach with a simple count
check in SpawnTool.execute(). When the concurrency limit is reached,
the tool returns an error string so the agent can perceive the reason
and adjust its behavior instead of silently queueing.

- Remove max_concurrent_subagents parameter threading through
  AgentLoop, commands.py, and nanobot.py
- SubagentManager reads the limit directly from AgentDefaults
- SpawnTool checks get_running_count() before calling spawn()
- Simplify tests to verify rejection behavior
2026-05-05 22:22:04 +08:00
Xubin Ren
861fbb0dde fix(provider): correct LongCat OpenAI base URL
Use the SDK-ready /v1 base so LongCat chat completions hit the documented endpoint.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-02 01:52:04 +08:00
moranfong
051037ff08 feat(provider): add LongCat via OpenAI-compatible backend 2026-05-02 01:52:04 +08:00
Xubin Ren
306958d6e6 add native Bedrock Converse provider
Made-with: Cursor
2026-05-01 18:52:03 +08:00
hanyuanling
0b111a0e0c fix(channels): support per-channel progress controls 2026-04-29 16:43:09 +08:00
chengyongru
28f9bbff31 feat(web_search): add olostep provider
Adds Olostep (https://www.olostep.com) as an optional web_search backend
using the official olostep Python SDK (client.answers.create()).

Changes:
- pyproject.toml: adds olostep>=0.1.0 optional dependency
- schema.py: adds olostep to provider comment in WebSearchConfig
- web.py: adds _search_olostep() with lazy import and provider branching
- docs/configuration.md: documents Olostep setup under web search config
- tests: unit tests for the new provider

Backward compatible: existing users see no behavior change unless they
opt into provider: "olostep". No hard dependency at runtime path.

Co-authored-by: umerkay <umerkk164@gmail.com>
2026-04-28 19:09:38 +08:00
Xubin Ren
278ef22776 docs(config): document provider extra body
Show how to configure OpenAI-compatible request body extensions such as sampling and chat template parameters.

Made-with: Cursor
2026-04-28 15:56:13 +08:00
Xubin Ren
18432c313f Merge origin/main into web-tools
Made-with: Cursor
2026-04-28 07:17:05 +00:00
Celina Hanouti
2b455b1e14 feat(providers): add Hugging Face inference provider 2026-04-28 14:55:28 +08:00
Xubin Ren
12b9782f3e docs(deployment): clarify container user and config directory usage 2026-04-27 11:07:34 +00:00
Xubin Ren
e31273ebaa Merge origin/main into fix/discord-allow-channel-threads
Made-with: Cursor
2026-04-27 09:26:24 +00:00
Xubin Ren
3d75aedcac Merge origin/main into fix/msteams-prune-stale-refs
Resolve the MSTeams stale-reference cleanup conflict by keeping the PR's locked, atomic sidecar-meta implementation and aligning the merged test expectation locally.

Made-with: Cursor
2026-04-27 07:29:48 +00:00
Xubin Ren
620d9e4f31 fix(slack): accept inbound file_share messages without dropping them
Slack inbound events with subtype=file_share were silently dropped, so
nanobot never saw messages that included attachments. Allow file_share
through, download Slack-private files using the bot token into the
local media dir, and pass them to the agent as media paths plus a
"[file: name]" / "[image: name]" placeholder in the content. Reject
responses that look like Slack's login HTML so an auth page is never
saved as if it were the user's file. Document the required files:read
scope alongside files:write so installs that read attachments are not
quietly missing the permission.
2026-04-27 07:11:11 +00:00
Xubin Ren
8a0917db7a fix(slack): polish thread UX and media support 2026-04-27 12:45:00 +08:00
Xubin Ren
038a140ad3 fix(slack): preserve thread context for proactive replies
Capture Slack thread metadata for cron and message-tool deliveries so replies stay in the originating thread, and hydrate first thread mentions with recent Slack context.

Made-with: Cursor
2026-04-27 02:10:38 +08:00
Xubin Ren
830211b5d4 docs: simplify macOS launchd setup
Made-with: Cursor
2026-04-25 19:36:20 +08:00
Xubin Ren
8a4c338a01 docs: tighten macOS launchd setup
Made-with: Cursor
2026-04-25 19:36:20 +08:00
choiking
41f7eae7b4 docs: add macOS launchd gateway setup 2026-04-25 19:36:20 +08:00
zhuzhh
fe928a0d94 feat(msteams): split ref storage into main+meta sidecar files
- Separate updated_at into a meta sidecar file (msteams_conversations_meta.json)
    to keep backward compatibility with legacy data that never had updated_at.
    On first upgrade, legacy refs are kept alive by initializing updated_at to now
    instead of purging them immediately.
  - Add cross-process locking via fcntl (with Windows fallback) to prevent
    concurrent writes from different gateway processes overwriting each other.
  - Add ref_touch_interval_s config (default 300s) to throttle how often
    successful sends refresh updated_at, preventing unnecessary I/O.
  - Touch active refs on send success to prevent them from expiring while in use.
  - Add _safe_float and _normalize_ref_record for robust schema migration.
  - All refs operations now use threading.RLock within a process.
2026-04-25 15:39:43 +08:00
zhuzhh
15e9d0471f feat(msteams): make ref pruning configurable and atomic 2026-04-25 12:58:04 +08:00
zhuzhh
106ae2cf1f fix(msteams): prune stale and unsupported conversation refs 2026-04-25 12:22:36 +08:00
Bongjin Lee
93ca791ac6 fix(discord): full thread support with session isolation and allowlist enforcement
Discord threads use their own channel IDs, so allowChannels was blocking
thread replies unless each thread ID was listed explicitly.

- Include the thread parent channel ID as an allowlist candidate
- Enforce allow_channels on slash commands (previously bypassed)
- Show parent channel ID in runtime context, reply to the thread
- Fix subagent cancel key via effective_key propagation
- Detect bot mentions via raw_mentions and reply-to-bot references
- Cache seen thread channels for outbound delivery
- Ignore system messages that become empty prompts
2026-04-23 04:05:39 +09:00
Xubin Ren
e3bca929fb fix(webui): left-align prose inside user message pill 2026-04-23 00:07:27 +08:00
Mizarka
4c25b739b5
docs: add new web tool settings 2026-04-22 09:42:03 +00:00
Mizarka
ec2f0ccfdb
feat(web-tools): add configurable User-Agent
Assisted-by: Jo'Zahir:Qwen3.6-35B-A3B
2026-04-22 09:11:57 +00:00
k
123d69bfb7 fix: allow specifying transcription language 2026-04-22 12:41:32 +08:00
chengyongru
d4e34f8c67 fix(commands): intercept non-priority commands during active turn
Non-priority slash commands (e.g. /new, /help, /dream-log) arriving
while a session has an active LLM turn were silently queued into the
pending injection buffer and later injected as raw user messages into
the LLM conversation. This caused the model to respond to "/new" as
plain text instead of executing the command.

Root cause: the run() loop only checked priority commands (/stop,
/restart, /status) before routing messages to the pending queue. All
other command tiers (exact, prefix) bypassed command dispatch entirely.

Changes:
- Add CommandRouter.is_dispatchable_command() to match exact/prefix
  tiers, mirroring the existing is_priority() pattern.
- In run(), intercept dispatchable commands before pending queue
  insertion and dispatch them directly via _dispatch_command_inline().
- Extract _cancel_active_tasks() from cmd_stop for reuse; cmd_new now
  cancels active tasks before clearing the session to prevent shared
  mutable state corruption from concurrent asyncio coroutines.
- Update /new semantics: stops active task first, then clears session.
- Update documentation in help text, docs, and Discord command list.
2026-04-21 21:50:37 +08:00
Xubin Ren
6c24f24e9e feat(models): add support for kimi-k2.6 with temperature override and update documentation 2026-04-20 18:18:06 +00:00
Xubin Ren
508e247c82 docs: remove feature showcase and update memory and Python SDK documentation for clarity and completeness 2026-04-19 19:25:05 +08:00
Xubin Ren
53fb3c199a docs: update README and docs for clarity and consistency 2026-04-19 19:25:05 +08:00
Xubin Ren
8ff7b56cb2 docs: refactor README into a docs-first landing page 2026-04-19 19:25:05 +08:00