Commit Graph
100 Commits
Author SHA1 Message Date
7c1aa5ae31 docs: refine AI contributor guidance
Clarify nanobot's preference for small core changes, reviewable PR boundaries, and careful handling of prompt/context surfaces so AI contributors preserve the project's maintenance philosophy.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 14:00:32 +08:00
Xubin Ren 3231aaf9ee fix(image): prevent duplicate delivery and replay artifacts 2026-05-09 05:45:13 +00:00
bbdf1db30d fix(webui): render generated images as rounded previews
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 23:48:01 +08:00
151c3d5ad0 fix(webui): restore chat selection after settings
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 23:48:01 +08:00
2cc32ca07c feat(webui): redesign settings and BYOK configuration
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 23:48:01 +08:00
Xubin RenandCursor 451d740849 fix(webui): polish delete dialog and sidebar toggles
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 13:28:34 +00:00
cbd5b06075 fix(memory): align replay overflow with history trimming
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 20:37:03 +08:00
24daf9a51c test(memory): accept replay window in consolidation assertion
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 20:37:03 +08:00
91ade9eaac fix(memory): consolidate history hidden by replay window
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 20:37:03 +08:00
2c830ca817 test(weixin): stabilize typing keepalive assertion
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 20:06:23 +08:00
e936ed48bd feat: add image generation tool and WebUI mode
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 20:06:23 +08:00
ac18a8baad feat(webui): add localized slash commands
Add a session-scoped slash command palette sourced from backend command metadata, and keep welcome-page quick actions localized across all WebUI languages.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-07 00:20:28 +08:00
790a03ec28 feat(webui): polish chat layout and titles
Align the WebUI sidebar and chat chrome with the updated design, and generate WebUI session titles asynchronously without blocking turns.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-06 22:20:35 +08:00
Xubin RenandXubin Ren e54fbfeb2a test(cron): avoid Windows timer race
Disable the externally updated cron job before yielding to the event loop so slow Windows CI cannot run the short-interval job before the test writes the update.
2026-05-06 00:43:00 +08:00
db14685a69 fix(agent): soften SSRF guard recovery
Keep private URL access blocked at the tool boundary, but return a clear non-retryable hint so the agent can recover conversationally instead of aborting the turn.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-06 00:43:00 +08:00
4db50f2e32 fix(channels): reject unauthorized inbound before side effects
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-05 23:16:36 +08:00
1813fc5021 test(telegram): cover silent allowlist rejection
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-05 23:16:36 +08:00
614b21368f fix(agent): tighten safety guard edge cases
Keep the /dev workspace guard exception scoped to the known benign device paths already handled by ExecTool, and add coverage that non-benign /dev targets still get blocked. Also add a streaming regression for tool_error responses so fatal tool failures are delivered by channels instead of being marked as already streamed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-04 01:25:52 +08:00
2a7433b7ec chore(runner): tighten workspace guard comments and Windows tests
Keep the workspace-boundary changes easier to review by trimming long explanatory comments down to short local notes. Also make the #3599 POSIX command regression skip on Windows and normalize workspace violation signatures to POSIX separators so the throttle tests are platform-stable.

Tests:
- uv run pytest tests/tools/test_exec_security.py tests/utils/test_workspace_violation_throttle.py -q
- uv run pytest -q

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-04 01:18:39 +08:00
b8406be215 fix(runner): soft workspace boundary + per-target throttle (#3493 #3599 #3605)
Replaces PR #3493's blanket fatal abort with a "tell the model + throttle
the bypass loop" policy.  Workspace-bound rejections are now ordinary
recoverable tool errors enriched with a structured "this is a hard policy
boundary" instruction; SSRF stays the only marker that aborts the turn.

Why the fatal-abort approach broke
----------------------------------
PR #3493 promoted every shell `_guard_command` and filesystem path-resolution
rejection to a turn-fatal RuntimeError.  Two of those messages (`path
outside working dir` and `path traversal detected`) are heuristic substring
scans on the raw command, so legitimate commands like `rm <ws>/x.txt
2>/dev/null` or `find . -type f` killed the user's turn (#3599).  On
channels with outbound dedupe (Telegram) the user just saw silence (#3605),
and the noise polluted the LLM's context until it started hallucinating
guard rejections on plain relative paths (#3597).

Why we still need *some* throttle
---------------------------------
The original #3493 pain point was real: the LLM, refused once, would
swap tools and try again -- read_file -> exec cat -> exec cp -> bash -c
-> ln -sf -> python -c open(...).  Just removing the fatal escape lets
that loop run wild until max_iterations.

What this commit does
---------------------
- `nanobot/utils/runtime.py`: add `workspace_violation_signature` and
  `repeated_workspace_violation_error`.  The signature normalizes
  filesystem `path` arguments and the first absolute path inside an
  exec command, so swapping tools against the same outside target hits
  the same throttle bucket.  Two soft attempts are allowed; the third
  attempt's tool result is replaced with a hard "stop trying to bypass"
  message that quotes the target path and tells the model to ask the
  user for help.

- `nanobot/agent/runner.py`: split classification into `_is_ssrf_violation`
  (still fatal) and `_is_workspace_violation` (now soft).  All three
  failure branches in `_run_tool` (prep_error / exception / Error
  result) route through a shared `_classify_violation` that bumps the
  per-turn workspace_violation_counts dict and either keeps the tool's
  own message or substitutes the throttle escalation.  `_execute_tools`
  now threads that dict alongside the existing external_lookup_counts.

- `nanobot/agent/tools/shell.py`: append a structured boundary note to
  every workspace-bound guard rejection (`working_dir could not be
  resolved`, `working_dir is outside`, `path outside working dir`,
  `path traversal detected`).  SSRF errors stay short and direct so the
  model doesn't try to "phrase around" them.  Existing `2>/dev/null`
  allow-list and benign device passthrough from the previous commit
  remain.

- `nanobot/agent/tools/filesystem.py`: append the same boundary note to
  the `outside allowed directory` PermissionError so read_file / write_file
  / list_dir errors give the LLM the same explicit hint.

Tests
-----
- `tests/utils/test_workspace_violation_throttle.py` (new): signature
  collapses across read_file/exec/python -c against the same path,
  different paths get independent budgets, escalation only fires after
  the third attempt.

- `tests/agent/test_runner.py`:
  - `test_runner_does_not_abort_on_workspace_violation_anymore` -- v2
    contract: filesystem PermissionError is now soft, runner moves to
    the next iteration and finalizes cleanly.
  - `test_is_ssrf_violation_remains_fatal` + the existing
    `test_runner_aborts_on_ssrf_violation` -- SSRF still aborts on the
    first attempt.
  - `test_runner_lets_llm_recover_from_shell_guard_path_outside` -- end
    to end recovery from `path outside working dir`.
  - `test_runner_throttles_repeated_workspace_bypass_attempts` -- four
    bypass attempts against the same outside target produce at least
    one `workspace_violation_escalated` event and the run completes
    naturally without aborting the turn.
  - The two `_execute_tools` direct-call tests now pass the new
    workspace_violation_counts dict.

- `tests/tools/test_tool_validation.py`: relax three `==` assertions
  to `startswith` + "hard policy boundary" substring check to match
  the new structured error messages.

- `tests/tools/test_exec_security.py` keeps the prior `2>/dev/null`
  regression and the `> /etc/issue` negative case from the previous
  commit on this branch -- they still pass under the new policy.

Coverage status: full pytest 2648 passed / 2 skipped (was 2638 / 2
on origin/main).  Ruff is clean for every file touched in this commit.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-04 01:18:39 +08:00
Xubin RenandXubin Ren 7742f8fbdc fix(runner): narrow workspace_violation fatal classification (#3599, helps #3605 #3597)
PR #3493 promoted every shell `_guard_command` rejection to a turn-fatal
RuntimeError. The two heuristic outputs in that list -- `path outside
working dir` and `path traversal detected` -- routinely false-positive on
benign constructs (e.g. `2>/dev/null`, quoted `..` arguments to sed/find,
absolute paths inside inline scripts), so legitimate workspace commands
silently kill the user's turn (#3599) and the agent never gets a chance
to retry with a different approach (#3605).

Two changes, both narrowly scoped:

- `ExecTool._guard_command` now skips a small allow-list of kernel device
  files (`/dev/null`, the standard streams, `/dev/random`, `/dev/fd/N`,
  ...) before the workspace path check, matched against the pre-resolve
  string so symlinks like `/dev/stderr -> /proc/self/fd/2` still hit the
  allow-list. Real outside writes such as `> /etc/issue` remain blocked.
- `AgentRunner._WORKSPACE_BLOCK_MARKERS` keeps only the four hard
  path-resolution errors from filesystem.py / shell.py and the SSRF
  marker. The two heuristic substrings move out of the fatal list, so
  the LLM sees them as ordinary tool errors and can self-correct in the
  next iteration. SSRF stays fatal because retrying an internal URL
  with a different phrasing would defeat the safety boundary.

Tests:
- `tests/tools/test_exec_security.py`: parametrized regression for the
  exact #3599 command sample plus other stdio redirects and device
  reads; explicit negative case asserts `> /etc/issue` is still blocked.
- `tests/agent/test_runner.py`: `_is_workspace_violation` no longer
  fatals on the two heuristic markers, plus an end-to-end case proving
  the runner hands the guard error back to the LLM and finalizes the
  next turn cleanly.
2026-05-04 01:18:39 +08:00
9a9e446f3f fix(cron): clean persistence lint issues
Keep the cron persistence hardening clean under ruff without changing behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-04 00:16:39 +08:00
66682eb46f test(cli): cover retry-wait interactive routing
Keep provider retry wait messages on the interactive progress path so they do not fall through as assistant responses.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-03 22:59:08 +08:00
7faa339902 fix(webui): keep existing package lockfile
Restore the npm lockfile that is already present on main so this PR only carries the WebUI turn-completion changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-03 22:28:40 +08:00
96da6d8190 fix(webui): tighten turn completion handling
Keep the new turn-end signal scoped to WebSocket clients, preserve pending tool-call state across trailing tool result rows, and drop the accidental npm lockfile from the Bun-based WebUI.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-03 22:28:40 +08:00
Xubin Ren 2fa15ccf1b fix: improve media failure diagnostics and token fallback coverage 2026-05-02 11:37:07 +00:00
Xubin Ren fde530de01 refactor(setup): enhance SKILL.md for upgrade process clarity 2026-05-02 07:40:29 +00:00
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
fd1a5a6267 test(provider): tidy Anthropic fallback imports
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-01 23:59:24 +08:00
Xubin RenandXubin Ren 539d82eadc test(tools): accept spawn origin message context
Made-with: Cursor
2026-05-01 20:09:59 +08:00
Xubin RenandXubin Ren 188e6df757 fix(utils): cover complete trailing think markers
Made-with: Cursor
2026-05-01 20:09:59 +08:00
Xubin RenandXubin Ren aea5948b11 fix(tools): tighten web fetch URL cleaning
Made-with: Cursor
2026-05-01 19:58:19 +08:00
Xubin RenandXubin Ren 43a58335f6 fix(provider): narrow DeepSeek reasoning history cleanup
Made-with: Cursor
2026-05-01 19:52:38 +08:00
Xubin Ren e157392250 fix(agent): scope subagent reply dedupe to origin message
Made-with: Cursor
2026-05-01 11:47:24 +00:00
Xubin Ren 1c24f10236 fix(skills): update restart instructions in upgrade process 2026-05-01 11:18:47 +00:00
Xubin RenandXubin Ren 39c38b593f refactor(tools): move file state lookup out of loop
Made-with: Cursor
2026-05-01 19:15:07 +08:00
Xubin RenandXubin Ren fae38319ca fix(tools): scope file state by session
Made-with: Cursor
2026-05-01 19:15:07 +08:00
Xubin RenandXubin Ren 6891a7a4d4 fix(skills): correct update setup commands
Made-with: Cursor
2026-05-01 19:02:26 +08:00
Xubin RenandXubin Ren 306958d6e6 add native Bedrock Converse provider
Made-with: Cursor
2026-05-01 18:52:03 +08:00
Xubin RenandXubin Ren f8fd9f0011 fix(feishu): keep streaming replies in existing topics
Made-with: Cursor
2026-04-30 13:42:37 +08:00
Xubin Ren 71eff09653 fix(whatsapp): refresh bridge when source changes 2026-04-30 04:18:31 +00:00
Xubin Ren d23bcae5a3 chore: update README with news for v0.1.5.post4 release 2026-04-29 11:12:50 +00:00
Xubin Ren 69bcf26ef4 chore: update README with news for v0.1.5.post3 release 2026-04-29 10:59:19 +00:00
Xubin Ren 0b1631f33d chore: bump version to 0.1.5.post3 and update README news
- pyproject.toml + __init__.py: 0.1.5.post2 → 0.1.5.post3
- README: add daily news entries for 2026-04-22 through 2026-04-28

Made-with: Cursor
2026-04-29 10:50:57 +00:00
Xubin RenandXubin Ren 3d7099b421 fix(memory): clean atomic write test hygiene
Made-with: Cursor
2026-04-29 16:57:50 +08:00
Xubin Ren 67b4d113c9 chore: update pyproject.toml 2026-04-29 08:25:09 +00:00
Xubin RenandXubin 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 f4d8783f5e test(web): cover configurable fetch behavior
Ensure custom user agents are applied to direct web requests and disabling Jina Reader forces the local readability path.

Made-with: Cursor
2026-04-28 07:25:47 +00:00
Xubin Ren 18432c313f Merge origin/main into web-tools
Made-with: Cursor
2026-04-28 07:17:05 +00:00
Xubin RenandXubin Ren 50698c3d1c test(telegram): cover local attachment filenames
Add a regression test for preserving the original basename when Telegram sends local media bytes.

Made-with: Cursor
2026-04-28 15:13:49 +08:00
Xubin RenandXubin Ren 48f3cc6390 fix(agent): stop on workspace violations from tool errors
Treat workspace and safety guard failures as fatal regardless of whether they arrive from tool preparation, returned tool output, or raised exceptions.

Made-with: Cursor
2026-04-28 15:13:27 +08:00
Xubin RenandXubin Ren ad4802600e refactor(config): make max messages default explicit
Use 120 as the config-level default and normalize zero back to that limit so session replay always receives an explicit message cap.

Made-with: Cursor
2026-04-28 14:54:32 +08:00
Xubin Ren 97981b911a fix(slack): skip empty progress messages that render as blank lines 2026-04-27 12:48:24 +00:00
Xubin Ren 12b9782f3e docs(deployment): clarify container user and config directory usage 2026-04-27 11:07:34 +00:00
Xubin RenandXubin Ren fdfecd3ba6 refactor(codex): name progress delta capability semantically
Use a provider capability name that describes user-visible progress delta support instead of the runner implementation detail.

Made-with: Cursor
2026-04-27 18:48:05 +08:00
Xubin RenandXubin Ren 2b886ffd1f fix(command): expose history in chat command menus
Made-with: Cursor
2026-04-27 18:23:35 +08:00
Xubin RenandXubin Ren 8ed10ac7df test(command): keep history tests lint-clean
Made-with: Cursor
2026-04-27 18:23:35 +08: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 82c5083b15 fix(slack): preserve DM thread routing and strip trailing newlines 2026-04-27 09:01:04 +00:00
Xubin Ren eb4b3d9e26 refactor(session): internalize history/file-cap knobs as constants
Move sessionHistoryMaxMessages, sessionHistoryMaxTokens, and
sessionFileMaxMessages out of user-facing config into internal
constants (HISTORY_MAX_MESSAGES=120, FILE_MAX_MESSAGES=2000).

- Remove 3 fields from AgentDefaults and config pipeline
- Sink enforce_file_cap into Session (was AgentLoop)
- Auto-derive token budget from context window (was configurable)
- Net -113 lines across 7 files; 723 tests green

Made-with: Cursor
2026-04-27 08:06:50 +00:00
Xubin Ren 29ebc2d355 Merge origin/main into feat/session-replay-file-cap-invariants
Preserve main's timestamp/tool-context replay semantics while keeping the PR's session history and file-cap budgets.

Made-with: Cursor
2026-04-27 07:32:00 +00:00
Xubin Ren 367a6db78c test(msteams): align stale-ref test with sidecar metadata
The PR stores ref freshness in the metadata sidecar, so the merged main test should assert updated_at there instead of in the refs payload.

Made-with: Cursor
2026-04-27 07:30:17 +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 311a7fe36e fix(session): stop training the model to parrot [Message Time: ...]
Past assistant turns in history were prefixed with "[Message Time: ...]"
just like user turns. The model treated these as in-context demos and
started prefixing its own replies with the same marker, leaking
metadata to the user. Prompt-level warnings could not beat dozens of
prior assistant samples.

Annotate only user turns and proactive deliveries
(_channel_delivery=True, i.e. cron / heartbeat pushes whose timing is
the whole point and which are too infrequent to act as demos). Adjacent
user-side timestamps still pin every normal assistant reply for
relative-time reasoning. The now-redundant identity.md warning is
removed along with the demonstration source.
2026-04-27 07:11:20 +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 RenandXubin Ren 7dcf83e389 test(agent): cover threaded subagent routing
Made-with: Cursor
2026-04-27 14:37:36 +08:00
Xubin RenandXubin Ren 9b6f3d7abc fix(agent): resolve message media against active workspace
Made-with: Cursor
2026-04-27 14:31:39 +08:00
Xubin Ren eeaec1f951 fix(agent): prevent message time metadata from leaking into replies 2026-04-27 06:23:43 +00:00
Xubin RenandXubin Ren d89a824769 docs(readme): keep Slack upload scope in chat app docs
Keep the root README focused on the main setup path and leave Slack-specific upload permissions in the chat apps guide.

Made-with: Cursor
2026-04-27 12:45:00 +08:00
Xubin RenandXubin Ren 8a0917db7a fix(slack): polish thread UX and media support 2026-04-27 12:45:00 +08:00
Xubin RenandXubin Ren 5e9b9b9818 fix(slack): skip thread context for slash commands so /restart is not buried
_with_thread_context prepends conversation history to the message
content.  This turned "/restart" into "Slack thread context...\n\n
Current message:\n/restart", which the command router could not match
as a priority command.  Skip the context enrichment when the stripped
text starts with "/".

Made-with: Cursor
2026-04-27 12:45:00 +08:00
Xubin RenandXubin Ren 1fe3f0eb22 fix(restart): preserve channel metadata across /restart so reply lands in thread
cmd_restart only persisted channel + chat_id across the os.execv boundary, so
when the new process announced "Restart completed" the OutboundMessage had
no Slack thread_ts and the reply fell back to the channel root.

Serialize msg.metadata into NANOBOT_RESTART_NOTIFY_METADATA, restore it on the
RestartNotice, and forward it to OutboundMessage so the completion message
follows the same routing as the original /restart invocation.

Made-with: Cursor
2026-04-27 12:45:00 +08:00
Xubin RenandXubin Ren 1ef41052da fix(cron): rephrase fire-time prompt so agent delivers a natural reminder
The old prompt framed cron firing as a "task triggered" status report,
which led the agent to reply with things like "Done  已提醒
U0AV8BJPV8D 喝水" — exposing the user id and reading like a system log
instead of a friendly reminder. Reword it to instruct the agent to
speak directly to the user and forbid status-style language.

Made-with: Cursor
2026-04-27 12:45:00 +08:00
Xubin RenandXubin Ren 4801f54f5b fix(cron): persist channel_meta and session_key across reloads
Without writing these fields into jobs.json, cron jobs created in a
Slack thread lost their thread_ts (and original session_key) after the
service was reloaded, so reminders fired into the channel root.

Made-with: Cursor
2026-04-27 12:45:00 +08:00
Xubin Ren 4a4ba1efc1 Merge branch 'main' into fix/session-history-timestamps
Made-with: Cursor
2026-04-26 18:13:11 +00:00
Xubin RenandXubin 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 7037764186 docs: clarify maintainer and contribution licensing 2026-04-26 18:01:55 +00:00
Xubin Ren df37a36174 fix(agent): expose session timestamps in model context
Include persisted turn timestamps when assembling LLM prompts so relative-date references like yesterday and today have concrete anchors.

Made-with: Cursor
2026-04-26 17:42:58 +00:00
Xubin Ren b2aec5528a refactor(agent): move provider refresh into subsystem owners 2026-04-26 14:18:37 +00:00
Xubin Ren f670da6c70 refactor(providers): move provider snapshot creation into factory 2026-04-26 14:05:13 +00:00
Xubin Ren 65b0ae81af Merge origin/main into webui-settings
Made-with: Cursor
2026-04-26 13:05:32 +00:00
Xubin RenandXubin Ren 82b8a3af7e fix(provider): handle incomplete DeepSeek reasoning history 2026-04-26 20:47:55 +08:00
Xubin RenandXubin Ren 3b82e14f85 fix(shell): preserve login PATH for path append
Made-with: Cursor
2026-04-26 20:32:38 +08:00
Xubin RenandXubin Ren 727086ddac test: tighten consolidation ratio coverage
Made-with: Cursor
2026-04-26 20:24:42 +08:00
Xubin RenandXubin Ren 6036355ac5 fix(message): limit session recording to proactive sends
Only mark message-tool deliveries for channel-session recording while cron jobs are running, avoiding duplicate session writes during normal user turns.

Made-with: Cursor
2026-04-26 20:08:21 +08:00
Xubin RenandXubin Ren 799db33517 fix(heartbeat): record proactive deliveries in channel sessions
Route heartbeat, cron, and message-tool deliveries through one gateway helper so user-visible proactive messages are available when the channel replies.

Made-with: Cursor
2026-04-26 20:08:21 +08:00
Xubin RenandXubin Ren 1e11b35b45 fix(providers): tighten local endpoint detection
Parse the endpoint host before disabling keepalive so public hostnames that merely contain private-network substrings keep the default connection pool behavior.

Made-with: Cursor
2026-04-26 16:14:24 +08:00
Xubin RenandXubin Ren d0e1b1393a fix(feishu): scope streaming buffers by message
Keep concurrent Feishu group replies from sharing one streaming card buffer when sessions are split by topic or top-level message.

Made-with: Cursor
2026-04-26 16:09:31 +08:00
Xubin Ren b440e76d2f feat(webui): add model settings runtime refresh 2026-04-25 18:05:06 +00:00
Xubin Ren a58d9fd357 feat(webui): render ask_user choices
Made-with: Cursor
2026-04-25 15:46:47 +00:00
Xubin RenandXubin Ren 403ce23d22 fix(agent): tighten ask_user CLI handling
Made-with: Cursor
2026-04-25 22:10:19 +08:00
Xubin RenandXubin Ren 3b1ea99ee1 fix(agent): render ask_user options without buttons
Made-with: Cursor
2026-04-25 22:10:19 +08:00
Xubin RenandXubin Ren cfc76ffbbf feat(agent): add ask_user tool
Made-with: Cursor
2026-04-25 22:10:19 +08:00
Xubin RenandXubin Ren 830211b5d4 docs: simplify macOS launchd setup
Made-with: Cursor
2026-04-25 19:36:20 +08:00
Xubin RenandXubin Ren 8a4c338a01 docs: tighten macOS launchd setup
Made-with: Cursor
2026-04-25 19:36:20 +08:00
Xubin Ren 39a5a77874 fix(feishu): send videos with media message type 2026-04-24 20:00:56 +00:00
Xubin RenandXubin Ren e52fe2a8e2 feat(webui): render video media attachments
Add signed media URLs to live WebSocket replies and teach the WebUI to classify and render video attachments, so bot-sent videos can play inline in both live chats and session history.

Made-with: Cursor
2026-04-25 03:20:40 +08:00
Xubin RenandXubin Ren be05189f39 feat(channels): add video support for Telegram and WebSocket
Telegram previously sent all video files as documents via send_document,
so users saw a file icon instead of an inline player. WebSocket only
accepted image MIME types, rejecting video uploads entirely.

Telegram:
- Recognize video extensions (mp4/mov/avi/mkv/webm/3gp) in _get_media_type
- Route videos through send_video with supports_streaming=True
- Add VIDEO/VIDEO_NOTE/ANIMATION to inbound message filters
- Add video MIME mappings to _get_extension
- Fix: local file sends now use _call_with_retry (previously no retry)

WebSocket:
- Expand upload MIME whitelist with video/mp4, video/webm, video/quicktime
- Add per-type size limits (_MAX_VIDEO_BYTES=20MB, _MAX_VIDEOS_PER_MESSAGE=1)
- Expand media serving endpoint to serve video with correct Content-Type

Agent:
- Add "video" to message tool media parameter description
- Add .mp4 example to identity.md system prompt

Made-with: Cursor
2026-04-25 02:20:13 +08:00
Xubin RenandXubin Ren 3441d5f89c test(anthropic): cover remaining opus-4-7 temperature branches
The existing test only verified the adaptive path. Add two more cases:
- enabled thinking (high): temperature must also be omitted
- no thinking (None): temperature must still be omitted

Made-with: Cursor
2026-04-24 15:33:59 +08:00
Xubin RenandXubin Ren 7f1913f619 fix(provider): add DeepSeek thinking toggle; backfill reasoning_content on legacy messages
Two issues with DeepSeek V4 thinking mode support:

1. Missing thinking parameter injection.
   DeepSeek V4 requires `extra_body: {"thinking": {"type": "enabled/disabled"}}`
   — identical to VolcEngine/BytePlus. The code had this for volcengine,
   byteplus, dashscope, minimax, and kimi but not DeepSeek. This means
   `reasoning_effort=minimal` (thinking off) silently has no effect.

   Root cause: the thinking-style→wire-format mapping was an if/elif chain
   on provider *names*. DeepSeek was forgotten.

   Fix: make the mapping declarative via `ProviderSpec.thinking_style`:
   - "thinking_type" → {"thinking": {"type": "..."}} (DeepSeek, Volc, BytePlus)
   - "enable_thinking" → {"enable_thinking": bool} (DashScope)
   - "reasoning_split" → {"reasoning_split": bool} (MiniMax)
   `_build_kwargs` now does a single dict lookup. Adding a new provider
   with an existing wire format requires zero changes to the function.

2. Legacy session messages crash thinking-mode requests.
   When a session was started without thinking mode (or with a different
   model), assistant messages lack reasoning_content. DeepSeek V4 in
   thinking mode rejects these with 400:
   "The reasoning_content in the thinking mode must be passed back to the API."
   This affects ALL assistant messages, not just those with tool_calls
   (despite the docs only mentioning the tool_calls case).

   Fix: `_build_kwargs` backfills `reasoning_content: ""` on every
   assistant message missing it, but only when thinking mode is active.
   This is semantically neutral — the model treats empty reasoning_content
   as "no thinking happened on that turn". The backfill only touches the
   in-memory request copy; session files on disk are untouched.

Tests: +5 (3 thinking toggle, 2 backfill). Full suite: 2377 passed.
Made-with: Cursor
2026-04-24 15:06:39 +08:00