When an agent task is cancelled (e.g. via /stop), the ExecTool was only
handling TimeoutError but not CancelledError. This left the child process
running as an orphan. Now CancelledError also triggers process.kill() and
waitpid cleanup before re-raising.
Seeding PATH in the env before bash -l caused /etc/profile
to skip its default PATH setup, breaking standard commands.
Move path_append to an inline export so the login shell
establishes a proper base PATH first.
Add regression test: ls still works when path_append is set.
Made-with: Cursor
The exec tool previously passed the full parent process environment to
child processes, which meant LLM-generated commands could access secrets
stored in env vars (e.g. API keys from EnvironmentFile=).
Switch from subprocess_shell with inherited env to bash login shell
with a minimal environment (HOME, LANG, TERM only). The login shell
sources the user's profile for PATH setup, making the pathAppend
config option a fallback rather than the primary PATH mechanism.
- Introduced a helper method `_for_each_hook_safe` to reduce code duplication in hook method implementations.
- Updated error logging to include the method name for better traceability.
- Improved the `SkillsLoader` class by adding a new method `_skill_entries_from_dir` to simplify skill listing logic.
- Enhanced skill loading and filtering logic, ensuring workspace skills take precedence over built-in ones.
- Added comprehensive tests for `SkillsLoader` to validate functionality and edge cases.
DDGS's internal `timeout=10` relies on `requests` read-timeout semantics,
which only measure the gap between bytes — not total wall-clock time.
When the underlying HTTP connection enters CLOSE-WAIT or the server
dribbles data slowly, this timeout never fires, causing `ddgs.text` to
hang indefinitely via `asyncio.to_thread`.
Since `asyncio.to_thread` cannot cancel the underlying OS thread, the
agent's session lock is never released, blocking all subsequent messages
on the same session (observed: 8+ hours of unresponsiveness).
Fix:
- Add `timeout` field to `WebSearchConfig` (default: 30s, configurable
via config.json or NANOBOT_TOOLS__WEB__SEARCH__TIMEOUT env var)
- Wrap `asyncio.to_thread` with `asyncio.wait_for` to enforce a hard
wall-clock deadline
Closes#2804
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dream Phase 2 uses fail_on_tool_error=True, which terminates the entire
run on the first tool error (e.g. old_text not found in edit_file).
Normal agent runs default to False so the LLM can self-correct and retry.
Dream should behave the same way.
- Added Jinja2 template support for various agent responses, including identity, skills, and memory consolidation.
- Introduced new templates for evaluating notifications, handling subagent announcements, and managing platform policies.
- Updated the agent context and memory modules to utilize the new templating system for improved readability and maintainability.
- Added a new dependency on Jinja2 in pyproject.toml.
- Add GitStore class wrapping dulwich for memory file versioning
- Auto-commit memory changes during Dream consolidation
- Add /dream-log and /dream-restore commands for history browsing
- Pass tracked_files as constructor param, generate .gitignore dynamically
Add "Solutions" category to consolidate prompt so trial-and-error
workflows that reach a working approach are captured in history for
Dream to persist. Remove overly broad "debug steps" skip rule that
discarded these valuable findings.
Replace single-stage MemoryConsolidator with a two-stage architecture:
- Consolidator: lightweight token-budget triggered summarization,
appends to HISTORY.md with cursor-based tracking
- Dream: cron-scheduled two-phase processor that analyzes HISTORY.md
and updates SOUL.md, USER.md, MEMORY.md via AgentRunner with
edit_file tools for surgical, fault-tolerant updates
New files: MemoryStore (pure file I/O), Dream class, DreamConfig,
/dream and /dream-log commands. 89 tests covering all components.
Different channels could theoretically share the same chat_id.
Check both channel and chat_id to avoid cross-channel reply issues.
Co-authored-by: layla <111667698+04cb@users.noreply.github.com>
When the message tool is used to send a message to a different chat_id
than the current conversation, it was incorrectly including the default
message_id from the original context. This caused channels like Feishu
to send the message as a reply to the original chat instead of creating
a new message in the target chat.
Changes:
- Only use default message_id when chat_id matches the default context
- When targeting a different chat, set message_id to None to avoid
unintended reply behavior