199 Commits

Author SHA1 Message Date
RongLei
a37bc26ed3 fix: restore GitHub Copilot auth flow
Implement the real GitHub device flow and Copilot token exchange for the GitHub Copilot provider.

Also route github-copilot models through a dedicated backend and strip the provider prefix before API requests.

Add focused regression coverage for provider wiring and model normalization.

Generated with GitHub Copilot, GPT-5.4.
2026-04-02 03:46:40 +08:00
Xubin Ren
5635907e33 feat(api): load serve settings from config
Read serve host, port, and timeout from config by default, keep CLI flags higher priority, and bind the API to localhost by default for safer local usage.
2026-03-29 15:32:33 +00:00
Xubin Ren
a0684978fb feat(api): add fixed-session OpenAI-compatible endpoint
Expose OpenAI-compatible chat completions and models endpoints through a single persistent API session, keeping the integration simple without adding multi-session isolation yet.
2026-03-29 14:48:52 +00:00
Xubin Ren
13d6c0ae52 feat(config): add configurable timezone for runtime context
Add agent-level timezone configuration with a UTC default, propagate it into runtime context and heartbeat prompts, and document valid IANA timezone usage in the README.
2026-03-25 22:07:14 +08:00
Xubin Ren
3dfdab704e refactor: replace litellm with native openai + anthropic SDKs
- Remove litellm dependency entirely (supply chain risk mitigation)
- Add AnthropicProvider (native SDK) and OpenAICompatProvider (unified)
- Merge CustomProvider into OpenAICompatProvider, delete custom_provider.py
- Add ProviderSpec.backend field for declarative provider routing
- Remove _resolve_model, find_gateway, find_by_model (dead heuristics)
- Pass resolved spec directly into provider — zero internal lookups
- Stub out litellm-dependent model database (cli/models.py)
- Add anthropic>=0.45.0 to dependencies, remove litellm
- 593 tests passed, net -1034 lines
2026-03-25 01:58:48 +08:00
Xubin Ren
186357e80c Merge branch 'main' into fix/workspace-scoped-cron-store
Keep cron state workspace-scoped while only migrating legacy jobs into the default workspace. This preserves seamless upgrades for existing installs without polluting intentionally new workspaces.
2026-03-24 02:41:58 +00:00
Xubin Ren
0ca639bf22 fix(cli): use discovered class for channel login 2026-03-24 01:11:33 +08:00
chengyongru
556b21d011 refactor(channels): abstract login() into BaseChannel, unify CLI commands
Move channel-specific login logic from CLI into each channel class via a
new `login(force=False)` method on BaseChannel. The `channels login <name>`
command now dynamically loads the channel and calls its login() method.

- WeixinChannel.login(): calls existing _qr_login(), with force to clear saved token
- WhatsAppChannel.login(): sets up bridge and spawns npm process for QR login
- CLI no longer contains duplicate login logic per channel
- Update CHANNEL_PLUGIN_GUIDE to document the login() hook

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 01:11:33 +08:00
ZhangYuanhan-AI
ebc4c2ec35 feat(weixin): add personal WeChat channel via ilinkai HTTP long-poll API
Add a new WeChat (微信) channel that connects to personal WeChat using
the ilinkai.weixin.qq.com HTTP long-poll API. Protocol reverse-engineered
from @tencent-weixin/openclaw-weixin v1.0.2.

Features:
- QR code login flow (nanobot weixin login)
- HTTP long-poll message receiving (getupdates)
- Text message sending with proper WeixinMessage format
- Media download with AES-128-ECB decryption (image/voice/file/video)
- Voice-to-text from WeChat + Groq Whisper fallback
- Quoted message (ref_msg) support
- Session expiry detection and auto-pause
- Server-suggested poll timeout adaptation
- Context token caching for replies
- Auto-discovery via channel registry

No WebSocket, no Node.js bridge, no local WeChat client needed — pure
HTTP with a bot token obtained via QR code scan.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 01:11:33 +08:00
Xubin Ren
2056061765 refine heartbeat session retention boundaries 2026-03-24 00:33:43 +08:00
flobo3
ba0a3d14d9 fix: clear heartbeat session to prevent token overflow
(cherry picked from commit 5c871d75d5b1aac09a8df31e6d1e04ee3d9b0d2c)
2026-03-24 00:33:43 +08:00
Xubin Ren
20494a2c52 refactor command routing for future plugins and clearer CLI structure 2026-03-23 16:48:42 +08:00
Xubin Ren
8f5c2d1a06 fix(cli): stop spinner after non-streaming interactive replies 2026-03-23 03:28:10 +00:00
Desmond Sow
f64ae3b900 feat(provider): add OpenVINO Model Server provider (#2193)
add OpenVINO Model Server provider
2026-03-23 11:07:46 +08:00
Xubin Ren
f2e1cb3662 feat(cli): extract streaming renderer to stream.py with Rich Live
Move ThinkingSpinner and StreamRenderer into a dedicated module to keep
commands.py focused on orchestration. Uses Rich Live with manual refresh
(auto_refresh=False) and ellipsis overflow for stable streaming output.

Made-with: Cursor
2026-03-23 10:20:41 +08:00
Xubin Ren
bd621df57f feat: add streaming channel support with automatic fallback
Provider layer: add chat_stream / chat_stream_with_retry to all providers
(base fallback, litellm, custom, azure, codex). Refactor shared kwargs
building in each provider.

Channel layer: BaseChannel gains send_delta (no-op) and supports_streaming
(checks config + method override). ChannelManager routes _stream_delta /
_stream_end to send_delta, skips _streamed final messages.

AgentLoop._dispatch builds bus-backed on_stream/on_stream_end callbacks
when _wants_stream metadata is set. Non-streaming path unchanged.

CLI: clean up spinner ANSI workarounds, simplify commands.py flow.
Made-with: Cursor
2026-03-23 10:20:41 +08:00
Xubin Ren
e79b9f4a83 feat(agent): add streaming groundwork for future TUI
Preserve the provider and agent-loop streaming primitives plus the CLI experiment scaffolding so this work can be resumed later without blocking urgent bug fixes on main.

Made-with: Cursor
2026-03-23 10:20:41 +08:00
Xubin Ren
48c71bb61e refactor(agent): unify process_direct to return OutboundMessage
Merge process_direct() and process_direct_outbound() into a single
interface returning OutboundMessage | None. This eliminates the
dual-path detection logic in CLI single-message mode that relied on
inspect.iscoroutinefunction to distinguish between the two APIs.

Extract status rendering into a pure function build_status_content()
in utils/helpers.py, decoupling it from AgentLoop internals.

Made-with: Cursor
2026-03-22 00:39:38 +08:00
Xubin Ren
a8176ef2c6 fix(cli): keep direct-call rendering compatible in tests
Only use process_direct_outbound when the agent loop actually exposes it as an async method, and otherwise fall back to the legacy process_direct path. This keeps the new CLI render-metadata flow without breaking existing test doubles or older direct-call implementations.

Made-with: Cursor
2026-03-21 16:07:14 +00:00
Xubin Ren
e430b1daf5 fix(agent): refine status output and CLI rendering
Keep status output responsive while estimating current context from session history, dropping low-value queue/subagent counters, and marking command-style replies for plain-text rendering in CLI. Also route direct CLI calls through outbound metadata so help/status formatting stays explicit instead of relying on content heuristics.

Made-with: Cursor
2026-03-21 15:52:10 +00:00
James Wrigley
9aaeb7ebd8 Add support for -h in the CLI 2026-03-21 02:36:48 +08:00
Xubin Ren
f44c4f9e3c refactor: remove deprecated memory_window, harden wizard display 2026-03-20 18:46:13 +08:00
Xubin Ren
c3a4b16e76 refactor: optimize onboard wizard - mask secrets, remove emoji, reduce repetition
- Mask sensitive fields (api_key/token/secret/password) in all display
  surfaces, showing only the last 4 characters
- Replace all emoji with pure ASCII labels for consistent cross-platform
  terminal rendering
- Extract _print_summary_panel helper, eliminating 5x duplicate table
  construction in _show_summary
- Replace 3 one-line wrapper functions with declarative _SETTINGS_SECTIONS
  dispatch tables and _MENU_DISPATCH in run_onboard
- Extract _handle_model_field / _handle_context_window_field into a
  _FIELD_HANDLERS registry, shrinking _configure_pydantic_model
- Return FieldTypeInfo NamedTuple from _get_field_type_info for clarity
- Replace global mutable _PROVIDER_INFO / _CHANNEL_INFO with @lru_cache
- Use vars() instead of dir() in _get_channel_info for reliable config
  class discovery
- Defer litellm import in model_info.py so non-wizard CLI paths stay fast
- Clarify README Quick Start wording (Add -> Configure)
2026-03-20 18:46:13 +08:00
chengyongru
45e89d917b fix(onboard): require explicit save in interactive wizard
Cherry-pick from d6acf1a with manual merge resolution.
Keep onboarding edits in draft state until users choose Done or Save and
Exit, so backing out or discarding the wizard no longer persists partial
changes.

Co-Authored-By: Jason Zhao <144443939+JasonZhaoWW@users.noreply.github.com>
2026-03-20 18:46:13 +08:00
chengyongru
a6fb90291d feat(onboard): pass CLI args as initial config to interactive wizard
--workspace and --config now work as initial defaults in interactive mode:
- The wizard starts with these values pre-filled
- Users can view and modify them in the wizard
- Final saved config reflects user's choices

This makes the CLI args more useful for interactive sessions while
still allowing full customization through the wizard.
2026-03-20 18:46:13 +08:00
chengyongru
f127af0481 feat: add interactive onboard wizard for LLM provider and channel configuration 2026-03-20 18:46:13 +08:00
MiguelPF
4e56481f0b add one-time migration for legacy global cron store
When upgrading, if jobs.json exists at the old global path and not yet
at the workspace path, move it automatically.  Prevents silent loss of
existing cron jobs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 10:16:06 +01:00
MiguelPF
c33e01ee62 fix(cron): scope cron job store to workspace instead of global directory
Replace `get_cron_dir()` with `config.workspace_path / "cron"` so each
workspace keeps its own `jobs.json`.  This lets users run multiple
nanobot instances with independent cron schedules without cross-talk.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 10:11:01 +01:00
Xubin Ren
b2a550176e feat(onboard): align setup with config and workspace flags 2026-03-17 05:42:49 +00:00
Xubin Ren
40a022afd9 fix(onboard): use configured workspace path on setup 2026-03-17 05:01:34 +00:00
Xubin Ren
c4cc2a9fb4 Merge remote-tracking branch 'origin/main' into pr-1136 2026-03-17 04:42:01 +00:00
Xubin Ren
db37ecbfd2 fix(custom): support extraHeaders for OpenAI-compatible endpoints 2026-03-17 04:28:24 +00:00
Peter van Eijk
93f363d4d3 qol: add version id to logging 2026-03-16 22:17:40 +08:00
Xubin Ren
2eceb6ce8a fix(cli): pause spinner cleanly before printing progress output 2026-03-16 22:17:29 +08:00
who96
9a652fdd35 refactor(cli): restore context manager pattern for spinner lifecycle
Replace manual _active_spinner + _pause_spinner/_resume_spinner with
_ThinkingSpinner class that owns the spinner lifecycle via __enter__/
__exit__ and provides a pause() context manager for temporarily
stopping the spinner during progress output.

Benefits:
- Restores Pythonic context manager pattern matching original code
- Eliminates duplicated start/stop boilerplate between single-message
  and interactive modes
- pause() context manager guarantees resume even if print raises
- _active flag prevents post-teardown resume from async callbacks
2026-03-16 22:17:29 +08:00
who96
48fe92a8ad fix(cli): stop spinner before printing tool progress lines
The Rich console.status() spinner ('nanobot is thinking...') was not
cleared when tool call progress lines were printed during processing,
causing overlapping/garbled terminal output.

Replace the context-manager approach with explicit start/stop lifecycle:
- _pause_spinner() stops the spinner before any progress line is printed
- _resume_spinner() restarts it after printing
- Applied to both single-message mode (_cli_progress) and interactive
  mode (_consume_outbound)

Closes #1956
2026-03-16 22:17:29 +08:00
chengyongru
a2acacd8f2 fix: add exception handling to prevent agent loop crash 2026-03-14 18:34:22 +08:00
Xubin Ren
411b059dd2 refactor: replace <SILENT_OK> with structured post-run evaluation
- Add nanobot/utils/evaluator.py: lightweight LLM tool-call to decide notify/silent after background task execution
- Remove magic token injection from heartbeat and cron prompts
- Clean session history (no more <SILENT_OK> pollution)
- Add tests for evaluator and updated heartbeat three-phase flow
2026-03-14 17:41:08 +08:00
SJK-py
4990c7478b suppress unnecessary cron notifications
Appends a strict instruction to background task prompts (cron and heartbeat) 
directing the agent to return a `<SILENT_OK>` token if there is nothing 
material to report. Adds conditional logic to intercept this token and 
suppress the outbound message to the user, preventing notification spam 
from autonomous background checks.
2026-03-14 17:41:08 +08:00
Peixian Gong
58fc34d3f4 refactor: use shutil.which() instead of shell=True for npm calls
Replace platform-specific shell=True logic with shutil.which('npm') to
resolve the full path to the npm executable. This is cleaner because:

- No shell=True needed (safer, no shell injection risk)
- No platform-specific branching (sys.platform checks removed)
- Works identically on Windows, macOS, and Linux
- shutil.which() resolves npm.cmd on Windows automatically

The npm path check that already existed in _get_bridge_dir() is now
reused as the resolved path for subprocess calls. The same pattern is
applied to channels_login().
2026-03-14 17:19:01 +08:00
Peixian Gong
805228e91e fix: add shell=True for npm subprocess calls on Windows
On Windows, npm is installed as npm.cmd (a batch script), not a direct
executable. When subprocess.run() is called with a list like
['npm', 'install'] without shell=True, Python's CreateProcess cannot
locate npm.cmd, resulting in:

  FileNotFoundError: [WinError 2] The system cannot find the file specified

This fix adds a sys.platform == 'win32' check before each npm subprocess
call. On Windows, it uses shell=True with a string command so the shell
can resolve npm.cmd. On other platforms, the original list-based call is
preserved unchanged.

Affected locations:
- _get_bridge_dir(): npm install, npm run build
- channels_login(): npm start

No behavioral change on Linux/macOS.
2026-03-14 17:19:01 +08:00
Xubin Ren
af65145bc8 fix(qq): add configurable message format and onboard backfill 2026-03-14 08:25:44 +00:00
Xubin Ren
dbdb43faff feat: channel plugin architecture with decoupled configs
- Add plugin discovery via Python entry_points (group: nanobot.channels)
- Move 11 channel Config classes from schema.py into their own channel modules
- ChannelsConfig now only keeps send_progress + send_tool_hints (extra=allow)
- Each built-in channel parses dict->Pydantic in __init__, zero internal changes
- All channels implement default_config() for onboard auto-population
- nanobot onboard injects defaults for all discovered channels (built-in + plugins)
- Add nanobot plugins list CLI command
- Add Channel Plugin Guide (docs/CHANNEL_PLUGIN_GUIDE.md)
- Fully backward compatible: existing config.json and sessions work as-is
- 340 tests pass, zero regressions
2026-03-14 16:13:38 +08:00
Tink
f5cf0bfdee Merge origin/main into feat/openai-compatible-session-isolation (resolve conflicts)
Resolved 6 conflicted files:
- loop.py: adopt MemoryConsolidator pattern from main, keep _isolated_memory_store
- web.py, base.py, helpers.py: merge both sides' imports
- pyproject.toml: keep both api and wecom optional deps
- test_consolidate_offset.py: adopt main's _make_loop helper and consolidate_messages signatures
- test_openai_api.py: remove tests for deleted _consolidate_memory method

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:29:44 +08:00
Xubin Ren
ca5047b602 feat(web): multi-provider web search + Jina Reader fetch 2026-03-13 05:44:16 +00:00
Re-bin
3fa62e7fda fix: remove duplicate dim/arrow prefix in interactive progress line 2026-03-12 15:38:39 +00:00
Re-bin
48c74a11d4 Merge remote-tracking branch 'origin/main' into pr-1930 2026-03-12 15:28:57 +00:00
chengdu121
d51ec7f0e8 fix: preserve interactive CLI formatting for async subagent output 2026-03-12 19:15:04 +08:00
Re-bin
254cfd48ba refactor: auto-discover channels via pkgutil, eliminate hardcoded registry 2026-03-11 14:23:19 +00:00
Re-bin
c72c2ce7e2 refactor: move generation settings to provider level, eliminate parameter passthrough 2026-03-11 09:47:04 +00:00