1577 Commits

Author SHA1 Message Date
Xubin Ren
51f11a8548 docs(agent): execute authorized tasks through verification 2026-07-24 19:25:52 +08:00
Xubin Ren
7e15c4c447 fix(agent): track inline subagent lifecycle 2026-07-24 19:13:51 +08:00
Xubin Ren
3a400e0207 feat(agent): support inline subagent consultation 2026-07-24 19:13:51 +08:00
Xubin Ren
8e4fe9cfaf fix(providers): preserve non-multimodal tool lists 2026-07-24 18:58:32 +08:00
Xubin Ren
07a81d70be fix(providers): preserve multimodal tool outputs 2026-07-24 18:58:32 +08:00
chengyongru
9aab94c766 Revert "fix: preserve pending message runtime context"
This reverts commit cad368f58512b444a93aaede0c5741a3488c9d98.
2026-07-24 14:47:50 +08:00
hamb1y
cad368f585 fix: preserve pending message runtime context 2026-07-24 12:28:13 +08:00
George Pickett
0b38c48399 feat(webui): add Parallel Search MCP preset 2026-07-24 12:26:37 +08:00
flyzstu
8bcab8885e
test(agent): use python3 in ExecTool workspace scope tests (#5064)
* test(agent): use python3 in ExecTool workspace scope tests (fixes #5062)

* test(agent): use python on Windows and python3 on POSIX in ExecTool workspace scope tests (fixes #5062)

* test(agent): share Python command fixture

---------

Co-authored-by: chengyongru <chengyongru.ai@gmail.com>
2026-07-24 09:42:38 +08:00
chengyongru
aae259c790
feat(webui): simplify model preset settings (#5061) 2026-07-24 00:55:06 +08:00
chengyongru
d993c81f08 test(webui): cover restricted media previews 2026-07-24 00:30:49 +08:00
AxelRay
78f4c132d9
fix(exec): extract absolute paths after equals sign in shell guard (#4594) 2026-07-23 23:58:01 +08:00
KDB
648fc92673 fix(exec): retain stale sessions after cleanup failure
Only remove idle exec sessions after process termination succeeds so later cleanup and shutdown paths can retry failed kills.
2026-07-23 23:55:00 +08:00
KDB
274613f064 fix(session): tolerate files removed during listing 2026-07-23 23:53:56 +08:00
Xubin Ren
c3ec2e665f test(documents): preserve nested block order 2026-07-23 16:53:59 +08:00
Xubin Ren
911a7e3a82 test(documents): cover vertical merged cells 2026-07-23 16:53:59 +08:00
Xubin Ren
fc9d17eb7b fix(documents): bound nested DOCX table parsing 2026-07-23 16:53:59 +08:00
Xubin Ren
60ab580f8b fix(documents): preserve DOCX table content 2026-07-23 16:53:59 +08:00
chengyongru
96eb965aae
feat(webui): show the actual fallback model (#5017) 2026-07-23 15:57:13 +08:00
axelray-dev
464f71b488 fix(session): fall back to legacy paths in metadata reads
Fixes #4940
2026-07-23 14:53:00 +08:00
chengyongru
15de6be0af fix(providers): fall back on authentication errors 2026-07-23 14:52:04 +08:00
Arthur K.
3647875aba fix: add one second to retry after delays 2026-07-23 14:10:27 +08:00
santhreal
299bcf491b fix(cron): skip null runHistory elements when loading jobs.json
Null entries in state.runHistory raised TypeError and quarantined the
store. Skip non-dict elements like LocalTrigger.from_dict already does.
2026-07-23 14:07:06 +08:00
santhreal
0191c0db73 fix(pairing): treat null approved channel lists as empty
pairing.json with "telegram": null crashed is_approved during load.
Treat non-list channel entries as an empty allow-list.
2026-07-23 14:04:53 +08:00
chengyongru
9cf2fb19c2
feat(xai): surface hosted X Search activity (#5050) 2026-07-23 13:42:09 +08:00
chengyongru
5f054c0e74 fix(agent): deliver non-streamed finalization responses 2026-07-23 13:16:47 +08:00
Xubin Ren
2f4f00bb9f refactor(image): reuse the model picker 2026-07-23 12:42:24 +08:00
Xubin Ren
1616fa9f14 feat(image): apply generation settings live 2026-07-23 12:42:24 +08:00
chengyongru
c7393c785e
feat(providers): add xAI Grok OAuth with capability-gated X Search (#5035) 2026-07-23 11:55:16 +08:00
chengyongru
c22efb5f7a
feat(agent): make model presets session-scoped (#4866) 2026-07-23 00:38:49 +08:00
chengyongru
66690fdb0c
fix(webui): deliver late subagent results as new turns (#4992) 2026-07-22 23:04:36 +08:00
Xubin Ren
aa8387fb4d feat(webui): polish agent output and app discovery 2026-07-22 22:42:31 +08:00
chengyongru
b189a37648
fix(agent): preserve agent-owned state in project workspaces (#4945) 2026-07-22 17:25:22 +08:00
chengyongru
80085085d9 fix(exec): retain failed owner session cleanup 2026-07-22 15:28:34 +08:00
yorkhellen
ebf1ef5cab test(subagent): verify cascade exec termination on /stop
- terminate_by_owner kills matching sessions, skips others, handles
  empty owner case
- cancel_by_session calls terminate_by_owner on the session key
2026-07-22 15:28:34 +08:00
chengyongru
3748f664b2
feat(config): watch runtime configuration changes (#5026) 2026-07-22 13:08:39 +08:00
seteiro
79d9455313
fix(providers): add Qwen model-level thinking style mapping (#5023)
Add _QWEN_THINKING_MODELS to _MODEL_THINKING_STYLES with enable_thinking style. Prevents Qwen 3.5/3.6/3.7 models from exposing raw reasoning content in chat responses. Closes #4934
2026-07-22 10:46:22 +08:00
hamb1y
a9867a5a4e fix: quarantine invalid tool results 2026-07-22 01:59:09 +08:00
yrk
be1cc769d5 docs: refine ModelScope documentation wording 2026-07-22 01:35:20 +08:00
yrk
9abad4746e feat(providers): add ModelScope provider for LLM and image generation 2026-07-22 01:35:20 +08:00
Kris Lu
89d8c055a8 fix(providers): sanitize UTF-16 surrogates at provider request boundary
Symptom
-------
LLM requests intermittently fail with:
  'utf-8' codec can't encode characters in position N-N+1: surrogates not allowed
when messages contain emoji-heavy content (e.g. HTML with mixed emoji + JSON round-trips).
This blocks the affected session until the session file is quarantined.

Root cause
----------
Surrogate sanitization was only applied at the CLI entry point
(nanobot/cli/commands.py: _sanitize_surrogates). Requests entering
the LLM provider layer through other channels (Feishu, cron, webui,
tool results, memory injection) had no defensive cleaning, so any
message that happened to carry unpaired UTF-16 surrogates (from an
upstream JSON round-trip with ensure_ascii=True on ill-formed input,
memory rehydration, or third-party content) would blow up at
json.dumps -> HTTP encode time inside the provider client.

Fix
---
1. Extract sanitize_surrogates() and sanitize_surrogates_deep() into
   nanobot/utils/helpers.py as the single source of truth. Both use
   utf-16-le round-tripping with errors='surrogatepass' / 'replace',
   so paired surrogates reconstruct back into their real code point
   and lone surrogates collapse to U+FFFD.
2. Make nanobot/cli/commands.py:_sanitize_surrogates a thin wrapper
   that re-exports the shared helper (backward compatible).
3. Add defense-in-depth at the LLM provider boundary in
   nanobot/providers/base.py:_sanitize_empty_content by running
   sanitize_surrogates_deep over each message and its content blocks
   right before requests are serialized to JSON.

Non-goals
---------
- truncate_text() is intentionally left untouched. Python str slicing
  cannot split a single code point into surrogate halves, so it is
  not the source of lone surrogates.
- session/manager storage layer is untouched. Archived sessions
  reproduced the failure only through the request path, not through
  storage.

Verification
------------
- New regression suite tests/providers/test_sanitize_surrogates.py
  covers: paired surrogate reconstruction, lone surrogate replacement,
  identity return on clean input (zero allocation), deep recursion on
  dict/list/tuple, provider _sanitize_empty_content integration, and
  full utf-8 encodability of the sanitized request body.
- 14/14 new tests pass; full existing test module also green.
- Replayed 58 archived real session messages plus adversarial
  lone-surrogate injection through the provider path with no encode
  errors after the fix.

Impact
------
- No behaviour change for clean inputs (sanitize_surrogates_deep is
  an identity return when no surrogate is present).
- Fails-safe: unpaired surrogates degrade to U+FFFD instead of
  aborting the entire request.
2026-07-21 19:17:58 +08:00
santhreal
b81c05581f fix(cron): coerce string schedule/state ms fields from jobs.json
jobs.json can store everyMs/atMs and next/last run timestamps as strings.
Loading left them as str, so _compute_next_run compared str to int and raised TypeError.
Coerce with an optional-int helper at from_store_dict, matching runHistory int() paths.
2026-07-21 19:07:45 +08:00
chengyongru
1d7bad3909 feat(providers): support Codex fast mode 2026-07-21 17:55:17 +08:00
Xubin Ren
b46e7f4377 fix(config): invalidate fields with missing env refs 2026-07-21 17:35:16 +08:00
Ben Lenarts
4cfc99f4b3 fix(transcription): resolve ${VAR} env refs in transcription api_key/api_base
config.loader.load_config() intentionally returns the raw config with ${VAR}
references intact — env interpolation is a separate, explicit step
(resolve_config_env_vars) so that settings read/edit/save paths never
materialize secrets to disk or to the UI.

The transcription config path does not apply that step: both
channels/base.py (channel voice notes) and webui/transcription_ws.py (WebUI
recording) build their effective config via
resolve_transcription_config(load_config()). As a result a configured
api_key of "${GROQ_API_KEY}" (the documented way to reference secrets) is
passed to the provider verbatim, which fails with 401 Invalid API Key. No
amount of rotating the real key helps, because the literal placeholder
string is what gets sent.

Resolve the reference at the single choke point both callers share —
_resolve_transcription_api_key / _resolve_transcription_api_base — using a
new lenient loader.resolve_env_refs() helper (unset var -> empty string, so
a missing variable degrades to "not configured" rather than raising or
leaking). This fixes both entry points at once and cannot drift the way a
per-call-site fix does. Resolving inside load_config() was rejected: the
~20 settings-UI callers depend on it returning raw ${VAR} placeholders.

Literal keys are unaffected; the settings API only reads the derived
`configured` flag (never the key), which now reflects the resolved value.

Claude-Session: https://claude.ai/code/session_01Q3HuVaJAAQJA3kgVQVJ2Zt
2026-07-21 17:35:16 +08:00
Xubin Ren
b2cf37da4a fix(config): preserve permissions during atomic save 2026-07-21 17:33:39 +08:00
santhreal
28102382af fix(config): write config.json atomically via temp+replace
save_config truncated config.json in place on crash mid-write.
Route through _write_text_atomic like the pairing store so a failed write leaves the prior file intact.
2026-07-21 17:33:39 +08:00
amplifierplus
cdb2df4982 fix(files): reject oversized reads before loading 2026-07-21 15:02:27 +08:00
chengyongru
d5658dbc91 fix: preserve background operator in allowlist segments
Maintainer edit: keep a top-level trailing '&' in the segment being matched so background execution cannot be checked as if the ampersand were absent. Redirection forms like 2>&1 and &> remain untouched.
2026-07-21 13:50:24 +08:00
chengyongru
12c52c11d3 fix: treat single ampersand as shell segment
Maintainer edit: single '&' backgrounds the preceding command and starts another top-level shell segment, so allowPatterns must split it the same way as ';', '|', '&&', and '||'. Keep fd redirections such as 2>&1 and &> intact.
2026-07-21 13:50:24 +08:00