Commit Graph
100 Commits
Author SHA1 Message Date
Xubin RenandXubin Ren 469fc90fe6 fix(agent): on_progress tool_events only when callback accepts; align progress tests with main
Made-with: Cursor
2026-04-23 20:06:11 +08:00
Xubin Ren 185a8fd34d fix(webui): opaque composer, equal-width message area, cleaner user pill 2026-04-23 07:48:32 +00:00
Xubin RenandXubin Ren 06503cd0fc fix(telegram): keep callback_data under Telegram's 64-byte cap
``InlineKeyboardButton(label, callback_data=label)`` fails Telegram's
API when the label exceeds 64 bytes UTF-8. An LLM-generated long
option (realistic in multilingual flows) used to 400 the ``send_message``
call silently — user got nothing, agent heard a successful retry-then-drop.

Decouple display from wire: button text keeps the full label, callback_data
gets truncated at a UTF-8 char boundary. Tap echoes the prefix back as the
user message; the LLM understands a prefix of its own option just fine,
and the display the user saw was always the full string.

Locks: helper boundary behavior (ASCII, CJK, short labels pass through)
and end-to-end ``_build_keyboard`` integration with an over-cap label.

Made-with: Cursor
2026-04-23 13:26:06 +08:00
Xubin RenandXubin Ren 6bc2983ab1 fix(telegram): fall back buttons to inline text when keyboard disabled
Buttons are semantic options, not a separate channel protocol: a user
who taps "Yes" and a user who types "yes" arrive at the agent as the
same string. Dropping ``msg.buttons`` when ``inline_keyboards=False``
was the worst of both worlds — the agent got told "Message sent with
N button(s)" while the user saw a question with no options.

Splice the labels into the message text instead. The LLM produces the
same ``message(buttons=...)`` call regardless of channel; the channel
layer picks the richest rendering it can afford — native keyboard when
enabled, bracketed inline text otherwise. Layout is preserved (one row
per line). Other channels can adopt the same helper incrementally.

Locks: canonical ``_buttons_as_text`` format, flag-off send-path
splices labels, flag-on send-path keeps content clean and rides
``reply_markup``.

Made-with: Cursor
2026-04-23 13:26:06 +08:00
Xubin RenandXubin Ren b9b81d9301 test(telegram): pin inline-keyboards flag gate and buttons validation
Two kill-switch tests for the new inline-keyboards path. Neither is
flashy — they just make sure the next unrelated refactor can't quietly
regress two narrow contracts the PR relies on.

  1. TelegramChannel._build_keyboard returns None whenever
     TelegramConfig.inline_keyboards is False, even if buttons are
     supplied. The flag defaults off; if someone ever flips that default
     the change should fail this test before it reaches prod bots.

  2. MessageTool rejects malformed `buttons` payloads (non-list, mixed
     list/str row, non-str label, None label) up front instead of
     letting them slip into the channel layer where Telegram would
     silently 400 the send. Parametrized over four shapes the guard
     needs to reject.

No production code touched.

Made-with: Cursor
2026-04-23 13:26:06 +08:00
Xubin RenandXubin Ren e3bca929fb fix(webui): left-align prose inside user message pill 2026-04-23 00:07:27 +08:00
Xubin RenandXubin Ren e493eb09e7 test(webui): realign thread-composer attach test with current types 2026-04-23 00:07:27 +08:00
Xubin RenandXubin Ren 707c0d7f3a fix(websocket): scrub partial media batches, nosniff /api/media 2026-04-23 00:07:27 +08:00
Xubin RenandXubin Ren 61a28c2c0a feat(webui): support image uploads in composer and message bubbles 2026-04-23 00:07:27 +08:00
Xubin RenandXubin Ren c1e7aa5504 refactor(config): resolve env vars via in-place Pydantic walk
Replace the dump→resolve→model_validate roundtrip with a recursive walk
that substitutes ${VAR} in string values directly on BaseModel /
__pydantic_extra__ / dict / list nodes. Identity is preserved on any
subtree with no references, so the original Config instance is returned
unchanged when nothing needs resolving.

Side effects:
- exclude=True fields (e.g. DreamConfig.cron) now survive even when
  other fields in the same config contain ${VAR} references, closing
  the edge case left open by the previous fast-path-only fix.
- _has_env_refs is dropped (the walker short-circuits naturally).
- Added a regression test pairing cron with a resolved providers.groq
  api_key to lock the coexistence case.

Made-with: Cursor
2026-04-22 22:31:40 +08:00
Xubin RenandXubin Ren 239e91a4d6 test(anthropic): pin tool_result image_url conversion regression
Adds a focused regression test so the fix for tool_result image
handling cannot silently revert. Two cases:

- list content with an image_url + text block -> image_url is
  translated to a native Anthropic image block, sibling text passes
  through unchanged
- plain string content passes through untouched (the new list branch
  must not alter the string path)

These cover the exact symptom surface (silent image drop with a
"Non-transient LLM error with image content" warning) and the only
two content shapes tool results actually take today.

Made-with: Cursor
2026-04-22 22:10:53 +08:00
Xubin Renandlahuman 7c21349828 Merge pull request #3379 from lahuman/fix/3324-windows-mcp-stdio
fix(mcp): avoid WinError 193 for Windows stdio launchers

Co-authored-by: lahuman <6156679+lahuman@users.noreply.github.com>
2026-04-22 08:09:46 +00:00
Xubin Ren 79247545ac Merge remote-tracking branch 'origin/main' into pr-3379 2026-04-22 08:08:05 +00:00
Xubin Renandgongpx20069 f718a71dcc Merge pull request #3380 from gongpx20069/fix/github-copilot-gpt5-support
fix(providers): support GPT-5 models on GitHub Copilot backend

Co-authored-by: gongpx20069 <21985921+gongpx20069@users.noreply.github.com>
2026-04-22 06:53:39 +00:00
Xubin Ren 427deb4a70 test(providers): add regression tests for GitHub Copilot /responses routing
Locks in the four behaviors introduced by the fix so they can't silently
revert:
- _should_use_responses_api accepts github_copilot on its non-OpenAI base
- _build_responses_body strips the 'github_copilot/' routing prefix
- /responses failures on github_copilot do not fall back to /chat/completions

Made-with: Cursor
2026-04-22 06:53:37 +00:00
Xubin RenandXubin Ren ef8bbab7b3 test(cli): lock _render_interactive_ansi force_terminal to isatty
Made-with: Cursor
2026-04-22 13:12:29 +08:00
Xubin RenandXubin Ren 88c619901e review(providers): tighten comments in reasoning_effort normalize path
Made-with: Cursor
2026-04-22 12:49:55 +08:00
Xubin Ren f5b8ee9f78 docs: update v0.1.5.post2 release news 2026-04-21 17:50:54 +00:00
Xubin Ren 950dddec49 chore: bump version to 0.1.5.post2 2026-04-21 17:25:08 +00:00
Xubin Ren 558aa98491 chore: temporary keep WebUI source-only 2026-04-21 14:33:44 +00:00
Xubin Ren 1b692debdc docs(webui): revise README to clarify WebSocket channel setup and sequence of startup steps 2026-04-21 12:46:17 +00:00
Xubin RenandXubin Ren c1957e14ff refactor(memory): centralize cursor validation behind a single gate
Move the non-int cursor guard out of the two consumer sites and into a
shared ``_iter_valid_entries`` iterator so the invariant lives in one
place.  Closes three gaps left by the original fix:

* ``bool`` is now rejected — ``isinstance(True, int)`` is ``True`` in
  Python, so the previous guard silently treated ``{"cursor": true}`` as
  cursor ``1``.
* Recovery now returns ``max(valid cursors) + 1``.  Under adversarial
  corruption "first int scanning in reverse" is not the same thing, and
  only ``max`` keeps the recovered cursor strictly greater than every
  legitimate cursor still on disk.
* Non-int cursors are logged exactly once per ``MemoryStore``.  Silently
  dropping corrupted entries hides the root cause (an external writer
  to ``memory/history.jsonl``); rate-limiting keeps the log clean when
  the same poisoned file is read every turn.

All 7 tests from the original fix pass unchanged; 3 new tests pin the
invariants above.

Made-with: Cursor
2026-04-21 14:02:53 +08:00
Xubin RenandXubin Ren 82aa9efc02 test(mcp): pin CancelledError short-circuits the retry loop
The retry branch is only reachable via `except Exception`, and
`CancelledError` inherits from `BaseException`, so today it naturally
bypasses the retry path and /stop still works.  Add one focused
regression test so any future refactor that widens the retry catch to
`BaseException`, re-orders the handlers, or adds `CancelledError` to
`_TRANSIENT_EXC_NAMES` fails CI instead of silently swallowing /stop.

Made-with: Cursor
2026-04-21 13:24:40 +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 RenandXubin Ren 009cce78ad fix(anthropic): also enforce leading-user + empty-array recovery
Extend `_merge_consecutive` so the three invariants from
`LLMProvider._enforce_role_alternation` all hold for Anthropic:

1. collapse consecutive same-role turns (unchanged)
2. no trailing assistant — Anthropic rejects prefill (unchanged)
3. no leading assistant — Anthropic requires the first turn be user
4. non-empty messages array — recover the last stripped assistant as a
   user turn when every turn got stripped, so callers don't hit a
   secondary "messages array empty" 400

Anthropic-specific wrinkle: `tool_use` blocks live inside `content` (not
a separate `tool_calls` field) and are illegal inside user turns, so
both recovery paths skip any message carrying them rather than silently
producing a malformed request.

Adds 4 unit tests covering the new branches, including the tool_use
opt-outs, and updates the existing `test_single_assistant_stripped` to
reflect the new rerouting contract.

Made-with: Cursor
2026-04-21 01:32:32 +08:00
Xubin RenandXubin Ren 00de55072d test(agent): exercise /stop cancellation through _dispatch
Add a regression test that actually runs the CancelledError branch of
AgentLoop._dispatch end-to-end and asserts the in-flight checkpoint is
materialized into session.messages before the cancellation unwinds.

The three existing tests call _restore_runtime_checkpoint directly, so
they pass even if the cancel-time restore is ever removed from
_dispatch. This new test is the one that actually locks the fix in
place.

Made-with: Cursor
2026-04-21 01:14:41 +08:00
Xubin Ren 26fd2c099a build: ship THIRD_PARTY_NOTICES and fix webui packaging in wheel 2026-04-20 08:22:10 +00:00
Xubin RenandXubin Ren 56a779c128 fix(session): repair read-only corrupt session paths 2026-04-20 00:17:50 +08:00
Xubin Ren 97ae9cb318 docs: refine README for WebUI development workflow clarity 2026-04-19 13:42:02 +00:00
Xubin Ren b3049f7323 fix(webui): stabilize empty session history state 2026-04-19 13:38:47 +00:00
Xubin Ren f9e1d92abd docs: update README and webui documentation for WebUI development workflow 2026-04-19 13:10:36 +00:00
Xubin Ren c4b3837c5f Merge remote-tracking branch 'origin/main' into nanobot-webui 2026-04-19 12:36:52 +00:00
Xubin Ren 46e11a68a7 test: speed up cron and restart timing tests
Replace fixed sleep-based waits with condition polling in cron tests and mock the restart delay in CLI restart tests to reduce suite runtime without changing behavior.
2026-04-19 12:35:57 +00:00
Xubin RenandXubin Ren b6d63fb1ec fix: normalize responses circuit breaker keys
Made-with: Cursor
2026-04-19 20:16:25 +08:00
Xubin RenandXubin Ren ccd6c05f71 fix: include pending summaries in consolidation estimates
Made-with: Cursor
2026-04-19 20:06:11 +08:00
Xubin RenandXubin Ren 54b659929e test: cover summary persistence after token consolidation
Made-with: Cursor
2026-04-19 20:06:11 +08:00
Xubin RenandXubin Ren 107eae14d7 docs: add badges for commit activity and closed issues in README 2026-04-19 19:25:05 +08:00
Xubin RenandXubin 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 RenandXubin Ren ed150a4228 docs: enhance README installation instructions for better readability 2026-04-19 19:25:05 +08:00
Xubin RenandXubin Ren 622c467839 docs: refine README description for clarity 2026-04-19 19:25:05 +08:00
Xubin RenandXubin Ren 53fb3c199a docs: update README and docs for clarity and consistency 2026-04-19 19:25:05 +08:00
Xubin RenandXubin Ren 8ff7b56cb2 docs: refactor README into a docs-first landing page 2026-04-19 19:25:05 +08:00
Xubin Ren 4650b23d75 feat(webui): add i18n support and locale switcher 2026-04-19 06:39:06 +00:00
Xubin Ren be10ba1f0d Merge remote-tracking branch 'origin/main' into nanobot-webui 2026-04-19 05:15:27 +00:00
Xubin Ren 384bad17b4 Merge origin/main into fix/config-default-api-base
Made-with: Cursor
2026-04-18 20:08:21 +00:00
Xubin Ren 9c0dc8b276 fix: drop generic repeated tool-call guard
The global guard changed baseline agent and subagent behavior without
proving a real no-progress loop. Keep this PR focused on the cron
contract hardening and validation fixes.

Made-with: Cursor
2026-04-18 19:59:58 +00:00
Xubin Ren adc1e843b4 Merge origin/main into fix/cron-contract-repeat-guard
Made-with: Cursor
2026-04-18 19:42:48 +00:00
Xubin RenandXubin Ren e08507f3ce fix: handle git worktrees in GitStore nested repo protection
Treat `.git` files the same as `.git` directories so GitStore refuses to initialize inside git worktrees, and add a focused regression test for that checkout shape.

Made-with: Cursor
2026-04-19 03:38:22 +08:00
Xubin Ren 1b211c7d3a Merge branch 'main' into nanobot-webui
Made-with: Cursor
2026-04-18 19:17:16 +00:00
Xubin Ren 8f8e41fe06 chore: ignore tsbuildinfo cache files 2026-04-18 18:55:05 +00:00
Xubin Ren 9ed3031a42 feat(webui): add initial webui with websocket chat flow 2026-04-18 18:51:53 +00:00
Xubin RenandXubin Ren 6bfb75ed03 feat(websocket): multiplex multiple chat_ids over a single connection 2026-04-18 16:49:12 +08:00
Xubin RenandXubin Ren 70a1279b86 test: pin retry-wait callback routing so internal heartbeats stay off channels
Add two focused regression tests for the retry-wait leak this PR fixes:

- tests/agent/test_runner.py::test_runner_binds_on_retry_wait_to_retry_callback_not_progress
  locks in that `AgentRunSpec.retry_wait_callback` (not `progress_callback`) is
  what `_build_request_kwargs` forwards to the provider as `on_retry_wait`.

- tests/channels/test_channel_manager_delta_coalescing.py::TestRetryWaitFiltering
  runs `_dispatch_outbound` end-to-end and asserts that `_retry_wait: True`
  messages never reach channel send.

Both tests fail on origin/main and pass with this PR's fix applied.

Made-with: Cursor
2026-04-18 13:50:05 +08:00
Xubin RenandXubin Ren c8d834a504 fix(loop): document subagent-followup persistence and guard empty content
- Add inline rationale for persisting before ContextBuilder and for
  passing current_message="" on subagent follow-ups (avoids
  double-projection after merge).
- Skip persistence for empty subagent content (no-op messages should
  not pollute history).
- Add regression test covering the empty-content guard.

Made-with: Cursor
2026-04-18 13:30:22 +08:00
Xubin RenandXubin Ren 14ee7cb121 style: revert unrelated Black-style formatting churn (#3220)
The earlier commits picked up a large amount of Black-style reformatting
(multi-line frozenset / keyword-arg wrapping / docstring blanks / removed
parens) on top of the actual guard fix. @chengyongru flagged it; the
first pass reverted some but not all.

This restores nanobot/providers/base.py, runner.py, heartbeat/service.py,
and utils/evaluator.py to origin/main and reapplies only the guard logic:

  - base.py: add should_execute_tools property
  - runner.py / heartbeat/service.py / utils/evaluator.py: route through it
    + log a warning when has_tool_calls but finish_reason is anomalous

Net diff vs main is now +87/-4 (was +211/-102) — roughly 30 lines of real
logic, which is what the PR is actually about.

Behavior unchanged from previous HEAD; full suite still 2014 passed.

Made-with: Cursor
2026-04-17 20:39:46 +08:00
Xubin RenandXubin Ren 9a569fdc6a style: collapse should_execute_tools docstring to one line
Made-with: Cursor
2026-04-17 20:39:46 +08:00
Xubin RenandXubin Ren b8d327dc41 test + docs: lock should_execute_tools guard semantics (#3220)
Two small follow-ups to the guard:

1. Fix the should_execute_tools docstring so it matches the actual code.
   The previous version said "Only execute when finish_reason explicitly
   signals tool intent" but the code also accepts finish_reason == "stop".
   Explain why (some compliant providers emit "stop" with legitimate tool
   calls — openai_compat_provider.py already mirrors this at lines ~633 /
   ~678 where ("tool_calls", "stop") are both treated as the terminal
   tool-call state). Without this, a strict "tool_calls"-only guard would
   regress 15 existing runner tests that construct LLMResponse with
   tool_calls but no explicit finish_reason (default = "stop").

2. Add tests/providers/test_llm_response.py. This locks the three cases:
   - no tool calls                  -> never executes
   - tool calls + "tool_calls"/stop -> executes
   - tool calls + refusal / content_filter / error / length / ... -> blocked

   These are exactly the boundary cases the #3220 fix is about; without a
   test here a future refactor could silently revert the guard.

Body + tests only, no behavior change beyond the existing PR's intent.

Made-with: Cursor
2026-04-17 20:39:46 +08:00
Xubin RenandXubin Ren ebbed1cbe2 fix(docs): depend on nanobot-ai, not the unrelated nanobot package
The PyPI package `nanobot` is a different project ("Minimalist robot
navigation framework"), not this one. This project publishes as
`nanobot-ai` (see pyproject.toml). Following the guide as-written would
pull down the wrong package — flagged by vansatchen in #3188.

Same toml block as the build-backend fix, one-word change.

Made-with: Cursor
2026-04-17 17:08:34 +08:00
Xubin RenandXubin Ren 3ae4333cef test(email): cover smtp_username / imap_username / case-insensitive self-address match
The original regression only exercised a from_address match with all three
identity fields set to the same value, so it couldn't distinguish whether
_self_addresses actually picks up smtp_username and imap_username or just
collapses on from_address. Add a parametrized test covering:

- smtp_username-only match (from_address empty, imap_username different) —
  simulates SMTP relays that rewrite outbound From to the login identity.
- imap_username-only match — simulates mailbox-identity setups.
- Case-insensitive match — inbound From arriving upper-cased must still hit.

No production code changes.

Made-with: Cursor
2026-04-17 16:25:16 +08:00
Xubin RenandXubin Ren e9d727c3a5 docs(readme): flag Matrix channel as unsupported on Windows
#3194 adds `; sys_platform != 'win32'` markers to `matrix-nio[e2e]` so
`pip install nanobot-ai[matrix]` no longer fails on Windows — but it also
no longer installs matrix-nio there. Without this note, Windows users get
a silent half-install and discover the limitation only when the channel
crashes at startup.

Made-with: Cursor
2026-04-17 16:11:37 +08:00
Xubin RenandXubin Ren 5badb75f6c review: tighten scope and add regression tests
Follow-ups from review of #3194:

- ci.yml: drop unconditional --ignore=tests/channels/test_matrix_channel.py.
  That test file already calls pytest.importorskip("nio") at module top, so
  it self-skips on Windows (where nio isn't installed) without also hiding
  62 tests from Linux CI.

- filesystem.py: hoist `import os` to the module top and drop the duplicate
  inline import in ReadFileTool.execute. Document the CRLF->LF normalization
  as intentional (primarily a Windows UX fix so downstream StrReplace/Grep
  match consistently regardless of where the file was written).

- test_read_enhancements.py: lock down two new behaviors
  * TestFileStateHashFallback: check_read warns when content changes but
    mtime is unchanged (coarse-mtime filesystems on Windows).
  * TestReadFileLineEndingNormalization: ReadFileTool strips CRLF and
    preserves LF-only files untouched.

- test_tool_validation.py: restore list2cmdline/shlex.quote in
  test_exec_head_tail_truncation. The temp_path-based form was correct,
  but dropping the quoting broke on any Windows path containing spaces
  (e.g. C:\Users\John Doe\...). CI runners happen not to have spaces so
  this slipped through.

Tests: 1993 passed locally.
Made-with: Cursor
2026-04-17 16:11:37 +08:00
Xubin RenandXubin Ren d57af5c1d1 test(channels): cover groq transcription api base propagation 2026-04-17 13:46:51 +08:00
Xubin RenandXubin Ren cc5a666d5d review(dream): harden line-age annotation per review feedback
Follow-up to #3212, fully backward compatible:

- Extract the 14-day staleness threshold as `_STALE_THRESHOLD_DAYS` module
  constant and pass it into the Phase 1 prompt template as
  `{{ stale_threshold_days }}`. The number lived in three places before
  (code threshold, prompt instruction, docstring); now there is one.
- Add `DreamConfig.annotate_line_ages` (default True = current behavior)
  and propagate it through `Dream.__init__` and the gateway wiring in
  cli/commands.py. Gives users a knob to disable the feature without a
  code patch if an LLM reacts poorly to the `← Nd` suffix.
- Harden `_annotate_with_ages` against dirty working trees: when HEAD
  blob line count disagrees with the working-tree content length, skip
  annotation entirely instead of assigning ages to the wrong lines. The
  previous `i >= len(ages)` guard only handled one direction of the
  mismatch.
- Inline-comment the `max_iterations` 10→15 bump with a pointer to
  exp002 so future blame has context.
- Add 4 regression tests: end-to-end `← 30d` reaches prompt, 14/15
  threshold boundary, `annotate_line_ages=False` bypasses git entirely
  (verified via `assert_not_called`), length-mismatch defense, and
  template-var rendering.

Made-with: Cursor
2026-04-17 13:45:38 +08:00
Xubin RenandXubin Ren ddf2fe443e docs(readme): document Discord allowChannels config field
Mention the new allowChannels field in the Discord config example and
add a TIP bullet explaining the empty-list default (respond in all
channels) and that it composes with allowFrom.

Made-with: Cursor
2026-04-17 02:14:33 +08:00
Xubin RenandXubin Ren 459a4d7311 test(discord): cover allow_channels filtering in _should_accept_inbound
Locks in the two key boundaries of the new channel-based filter:

1. When an incoming channel id is in allow_channels, messages are forwarded.
2. When an incoming channel id is not in allow_channels, messages are
   silently dropped.

The empty-list backward-compatible path is already covered by every
existing test that omits allow_channels (default_factory=list).

Made-with: Cursor
2026-04-17 02:14:33 +08:00
Xubin RenandXubin Ren 619c7fc20b docs(readme): reflect SSE streaming in OpenAI-compatible API section
The Behavior bullet previously claimed `stream=true` is not supported.
With this PR, /v1/chat/completions returns text/event-stream with
OpenAI-compatible delta chunks when stream=true, so flip the bullet
to describe the actual behavior instead of lying to readers.

Made-with: Cursor
2026-04-17 01:54:49 +08:00
Xubin Ren db78574cb8 docs(README): update auto compact section to clarify session file behavior and mental model 2026-04-16 17:20:38 +00:00
Xubin Ren 90b7d940e8 refactor(config): nest MyTool settings under tools.my (with legacy-key migration) 2026-04-16 15:58:20 +00:00
Xubin RenandXubin Ren 7ce8f247a0 test(api): cover remote image URL rejection 2026-04-16 21:02:33 +08:00
Xubin RenandXubin Ren a2f4090e41 fix(msteams): secure inbound defaults and ref persistence
Default Microsoft Teams inbound auth validation to enabled, update the README to match, and prevent denied senders from persisting conversation refs before allowlist checks pass.

Made-with: Cursor
2026-04-16 13:22:07 +08:00
Xubin RenandXubin Ren 2b8e90d8fd test(config): cover LM Studio nullable api key 2026-04-16 02:49:54 +08:00
Xubin Ren d46c1b14b0 docs: update .gitignore 2026-04-15 18:17:18 +00:00
Xubin RenandXubin Ren a6ea06e6bf docs(providers): explain MiniMax thinking endpoint
Document why MiniMax thinking mode uses a separate Anthropic-compatible provider and list the matching base URLs. Add a small registry test so the new provider stays wired to the expected backend and API key.

Made-with: Cursor
2026-04-16 01:00:45 +08:00
Xubin Ren 5683c79a6e chore: update README with new release notes of v0.1.5.post1 2026-04-14 19:01:43 +00:00
Xubin Ren 6483071485 chore: update version to 0.1.5.post1 2026-04-14 18:51:04 +00:00
Xubin Ren 1a5a16d1f3 chore: update README with recent news entries 2026-04-14 18:19:30 +00:00
Xubin RenandXubin Ren a0812ad60e test: cover retry termination notifications
Lock the new interaction-channel retry termination hints so both exhausted standard retries and persistent identical-error stops keep emitting the final progress message.

Made-with: Cursor
2026-04-15 01:55:57 +08:00
Xubin RenandXubin Ren 25ded8e747 test: cover active task count in status
Lock the /status task counter to the actual stop scope by asserting it sums unfinished dispatch tasks with running subagents for the current session.

Made-with: Cursor
2026-04-15 01:49:42 +08:00
Xubin RenandXubin Ren b60e8dc0ba test: cover missing tool-call arguments normalization
Lock the strict-provider sanitization path so assistant tool calls without function.arguments are normalized to {} instead of being forwarded as missing values.

Made-with: Cursor
2026-04-15 01:37:41 +08:00
Xubin RenandXubin Ren 1f33df1ea6 fix: preserve empty dict allow_from handling
Keep dict-backed channel configs compatible with both allow_from and allowFrom without losing empty-list semantics, and add focused regression coverage for the allow-list boundary.

Made-with: Cursor
2026-04-15 01:26:51 +08:00
Xubin Ren 89bf5d29d1 fix: reduce CLI streaming flicker and show model in welcome line 2026-04-14 13:38:06 +00:00
Xubin Ren c937c07178 fix: two bugs in document extraction pipeline
Bug 1: _drain_pending did not call extract_documents on follow-up
messages arriving mid-turn. Documents attached to queued messages were
silently dropped because _build_user_content only handles images.
Fix: call extract_documents before _build_user_content in _drain_pending.

Bug 2: extract_documents read the entire file into memory (up to 50 MB)
just to check 16 bytes of magic header for MIME detection.
Fix: read only the first 16 bytes via open()+read(16) instead of
Path.read_bytes().

Added regression tests for both bugs.

Made-with: Cursor
2026-04-14 13:15:04 +00:00
Xubin Ren 92d6fca323 refactor: centralize document extraction in AgentLoop._process_message
Move extract_documents() to nanobot.utils.document as a reusable helper
and call it once in AgentLoop._process_message, the single entry point
for all message processing (API + all channels).

This replaces the previous API-only _extract_documents() in server.py,
ensuring Telegram, Feishu, Slack, WeChat, and all other channels also
benefit from automatic document text extraction.

Adds a configurable max_file_size guard (default 50 MB) to skip
oversized files gracefully, preventing unbounded memory/CPU usage
from channel-downloaded attachments.

- server.py: removed _extract_documents and related imports
- document.py: added extract_documents() with size limit
- loop.py: calls extract_documents() at the top of _process_message
- Tests updated: 70 related tests pass

Made-with: Cursor
2026-04-14 13:10:03 +00:00
Xubin Ren 47f5795708 refactor: move document extraction from ContextBuilder to API layer
ContextBuilder._build_user_content now only handles images (its original
responsibility).  Document text extraction (PDF, DOCX, XLSX, PPTX) is
performed by the new _extract_documents() helper in server.py, called
before process_direct().  This keeps the core context builder free of
format-specific dependencies and makes the API boundary the single place
where uploaded files are pre-processed.

Tests updated to reflect the new responsibility boundary.

Made-with: Cursor
2026-04-14 13:00:59 +00:00
Xubin Ren 2502fc616b Merge origin/main into feat/api-file-upload
Keep the API file upload branch current with main, enforce the documented JSON base64 per-file limit, and avoid leaking document extraction error strings into user prompts.

Made-with: Cursor
2026-04-14 12:29:43 +00:00
Xubin RenandXubin Ren 0a51344483 fix(slack): keep cross-target sends out of origin threads
When Slack resolves a named target to another conversation, do not reuse the origin thread timestamp on the destination send, and keep reaction cleanup anchored to the source conversation.

Made-with: Cursor
2026-04-14 20:19:48 +08:00
Xubin Ren e4b3f9bd28 security(gateway): keep health endpoint local by default
Bind the gateway health listener to localhost by default and reduce the probe response to a minimal status payload so accidental public exposure leaks less information.

Made-with: Cursor
2026-04-14 07:19:38 +00:00
Xubin Ren 4999e2f734 Merge origin/main into feat/health-endpoint
Keep the gateway health endpoint patch current with the latest gateway runtime changes, and lock the new HTTP routes in with CLI regression coverage and README guidance.

Made-with: Cursor
2026-04-14 06:32:31 +00:00
Xubin RenandXubin Ren a38bc637bd fix(runner): preserve injection flag after max-iteration drain
Keep late follow-up injections observable when they are drained during max-iteration shutdown so loop-level response suppression still makes the right decision.

Made-with: Cursor
2026-04-14 00:30:30 +08:00
Xubin RenandXubin Ren 6484c7c47a fix(agent): close interrupted early-persisted user turns
Track text-only user messages that were flushed before the turn loop completes, then materialize an interrupted assistant placeholder on the next request so session history stays legal and later turns do not skip their own assistant reply.

Made-with: Cursor
2026-04-13 10:26:09 +08:00
Xubin RenandXubin Ren b964a894d2 test(agent): cover early user-message persistence
Use session.add_message for the pre-turn user-message flush and add focused regression tests for crash-time persistence and duplicate-free successful saves.

Made-with: Cursor
2026-04-13 10:26:09 +08:00
Xubin RenandXubin Ren 49355b2bd6 test(tools): lock non-object parameter validation
Add focused registry coverage so the new read_file/read_write parameter guard stays actionable without changing generic validation behavior for other tools.

Made-with: Cursor
2026-04-13 09:55:05 +08:00
Xubin RenandXubin Ren 217e1fc957 test(retry): lock in-place image fallback behavior
Add a focused regression test for the successful no-image retry path so the original message history stays stripped after fallback and the repeated retry loop cannot silently return.

Made-with: Cursor
2026-04-12 20:10:06 +08:00
Xubin RenandXubin Ren 7a7f5c9689 fix(dream): use valid builtin skill template paths
Point Dream skill creation at a readable builtin skill-creator template, keep skill writes rooted at the workspace, and document the new skill discovery behavior in README.

Made-with: Cursor
2026-04-12 16:49:55 +08:00
Xubin RenandXubin Ren 5dc238c7ef fix(shell): allow read-only copies from internal state files
Keep the new exec guard focused on writes to history.jsonl and .dream_cursor while still allowing read-only copy operations out of those files.

Made-with: Cursor
2026-04-12 16:38:55 +08:00
Xubin Ren a142788da9 docs(readme): document disabledSkills config
Explain the new agents.defaults.disabledSkills option so users can discover and configure skill exclusion from the main agent and subagents.

Made-with: Cursor
2026-04-12 02:42:52 +00:00
Xubin Ren e229c2ebc0 fix(pr): remove internal .docs file from PR
Keep the local review note out of the GitHub diff while preserving the actual code and test changes for this PR.

Made-with: Cursor
2026-04-12 02:21:46 +00:00
Xubin Ren 09c238ca0f Merge origin/main into pr-2959
Resolve the config plumbing conflicts and keep disabled skill filtering consistent for subagent prompts after syncing with main.

Made-with: Cursor
2026-04-12 02:02:39 +00:00
Xubin RenandXubin Ren cf8381f517 feat(agent): enhance message injection handling and content merging 2026-04-11 21:43:23 +08:00
Xubin RenandXubin Ren f6c39ec946 feat(agent): enhance session key handling for follow-up messages 2026-04-11 21:43:23 +08:00