SessionManager.save() previously used bare open("w") which could
truncate the JSONL file if the process crashed mid-write. Now writes
to a .tmp file and atomically replaces via os.replace(), matching the
pattern already used in qq.py.
_load() now attempts _repair() before returning None, recovering
valid lines from partially-written files. 12 new tests cover atomic
save correctness, temp-file cleanup on failure, and repair of
truncated/corrupt JSONL.
cowork-with:opencode(glm-5.1)
The old test `test_on_message_ignores_bot_messages` asserted the
previous (incorrect) contract that ALL bot-authored messages are
dropped. With #3217 only self-loops are dropped, so this test was
replaced with three more precise tests:
- test_on_message_ignores_self_messages: verifies self-loop guard
(author_id == _bot_user_id is dropped)
- test_on_message_accepts_messages_from_other_bots: new test for
the fix itself — other bots' messages flow through
- test_on_message_stops_typing_on_handle_exception: preserves the
typing cleanup assertion from the original test
Net result: +1 behavior tested, same behaviors retained.
Co-authored with Claude Opus 4.7
Previously the Discord channel dropped every message from any bot
account via `if message.author.bot`, which prevented legitimate
multi-agent setups (one bot asking another for help, bot-to-bot
@mentions, etc.) from working.
Narrow the guard to only drop messages from this bot's own account
by comparing against self._bot_user_id (already populated in on_ready).
Self-loop protection is preserved — each bot instance still ignores
its own outbound messages.
Co-authored with Claude Opus 4.7
PR #3125 added a top-level `oneOf` branch to `_CRON_PARAMETERS` to
advertise per-action required fields. OpenAI Codex/Responses rejects
`oneOf`/`anyOf`/`allOf`/`enum`/`not` at the root of function
parameters, so any agent that registers the cron tool now fails to
start with:
HTTP 400: Invalid schema for function 'cron': schema must have
type 'object' and not have 'oneOf'/'anyOf'/'allOf'/'enum'/'not'
at the top level.
Remove the top-level `oneOf`. The original intent of #3125 (stop LLMs
from looping on the #3113 contract mismatch) is preserved by:
- `validate_params` — runtime-enforces `message` for `action='add'`
and `job_id` for `action='remove'`
- field descriptions — each schema field already flags
"REQUIRED when action='...'" so the LLM sees the contract
The regression test is updated to lock the invariant in the other
direction: the top-level schema must not contain
`oneOf`/`anyOf`/`allOf`/`not`, and the REQUIRED hints must stay on
`message` and `job_id`.
Verified:
- tests/cron/ 70 passed
- tests/agent/test_loop_cron_timezone.py + tests/providers/ 232 passed
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
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.
When the Responses API fails repeatedly (3 consecutive compatibility
errors), skip it and fall back directly to Chat Completions. Unlike a
permanent disable, the circuit re-probes after 5 minutes so recovery
is automatic when the API comes back. Success resets the counter.
Keyed per (model, reasoning_effort) so a failure with one model does
not affect others.
- Track last_summary in maybe_consolidate_by_tokens() to persist the summary
- Change return to break in the consolidation loop to allow summary persistence
- Save summary to session.metadata['_last_summary'] for consistency with AutoCompact._archive()
- Ensures compressed content remains visible to the model via prepare_session() injection
Fixes#3274
The old test `test_make_console_uses_force_terminal` hardcoded
`force_terminal is True`, which contradicts the fix: we now defer
to sys.stdout.isatty() so piped / non-TTY output gets plain text
instead of ANSI escape codes.
Split into two tests covering both branches:
- test_make_console_force_terminal_when_stdout_is_tty: TTY path
(force_terminal=True, rich output)
- test_make_console_force_terminal_false_when_stdout_is_not_tty:
non-TTY path (force_terminal=False, plain text) — regression
guard for the bug reported in #3265
Co-authored with Claude Opus 4.7
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
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
GitStore.init() now checks if the workspace is already inside a git
repository before calling porcelain.init(). If so, it refuses to create
a nested repo. Additionally, existing .gitignore files are preserved
by appending only missing Dream-specific entries rather than overwriting.
Closes#2980
Add structured issue templates for bug reports and feature requests,
with dropdown menus for channel, LLM provider, Python version, and OS.
Redirect questions to Discussions. Add PR template with checklist.
Ref: https://github.com/HKUDS/nanobot/discussions/3284
Literal["standard", "persistent"] fields are now rendered as select
dropdowns instead of free-text input. This makes provider_retry_mode
and any future Literal fields self-documenting in the wizard.
- Add [H] Channel Common menu to configure send_progress, send_tool_hints,
send_max_retries, and transcription_provider
- Add [I] API Server menu to configure host, port, timeout
- Add real-time Pydantic field constraint validation (ge/gt/le/lt/min_length/max_length)
with constraint hints shown in field display (e.g. "Send Max Retries (0-10)")
- Add _pause() to View Configuration Summary to prevent immediate screen clear
- Fix _format_value dict branch to handle BaseModel instances without crashing
Move all behavioral instructions out of identity.md into SOUL.md so that
each file has a single clear purpose:
- identity.md: capability facts only (runtime, workspace, format hints,
tool guidance, untrusted content warning)
- SOUL.md: behavioral rules (name, personality, execution rules)
The "Act, don't narrate" rule is refined into layered behavior: act
immediately on single-step tasks, plan first for multi-step tasks. This
eliminates the contradiction where identity said "never end with a plan"
but user SOUL.md said "always plan first".
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
- 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