When an MCP server (e.g. stdio browser-agent subprocess) does not
terminate within the AsyncExitStack.aclose() timeout, asyncio raises
CancelledError. The existing exception handler only caught RuntimeError
and BaseExceptionGroup, so CancelledError escaped the except block and
crashed nanobot with exit code 1 on every shutdown.
Add asyncio.CancelledError to the caught exception tuple so the error
is logged at debug level and shutdown completes cleanly.
Stack trace from the crash:
Traceback (most recent call last):
File ".../nanobot/agent/loop.py", line 1194, in close_mcp
await stack.aclose()
asyncio.exceptions.CancelledError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File ".../asyncio/__main__.py", line ?, in <module>
File ".../nanobot/agent/loop.py", line ?, in close_mcp
...
RuntimeError: ... (or BaseExceptionGroup) not caught
SystemExit: 1
Send the overflow tail message with HTML parse_mode, but persist the
unrendered markdown chunk in buf.text so later deltas and stream_end
re-render correctly via _split_telegram_markdown_html().
Translate the WebUI common.json to pt-BR, register the locale in
config.ts (with normalizeLocale rules for "pt", "pt-BR", "pt-PT", ...)
and index.ts resources, and add a Brazilian Portuguese overview
assertion in i18n.test.tsx.
Notes:
- Generic "pt" and any "pt-*" variant resolve to pt-BR. A future
pt-PT locale can be added without affecting existing users.
- Universally borrowed technical terms (nanobot, BYOK, MCP, JSON,
URL, Web, Apps when used as a product name, Skills) are kept in
English, matching the pattern used by es/fr/ja/ko/vi/id.
- Empty-thread greetings, quick-action prompts, and slash command
descriptions are translated; quick-action prompts are intentionally
phrased so the agent still receives a clear task spec.
Verified with bun run test (536 passing), bun run lint (clean), and
bun run build (7.18s).
Replace the legacy long-goal skill contract with command-scoped goal tools and runtime guidance. Keep goal state durable across continuations while restricting create and replace mutations to explicit user /goal turns.
Maintainer edit: the real MCP reconnect regression test can bind the class-level PinnedDNSAsyncTransport lock to its pytest event loop. Patch the MCP module to use a test-local transport subclass so later async tests do not inherit that loop-bound lock.
maintainer edit: port the real streamable-http idle-timeout reproduction from #4764 so this PR carries regression coverage for #4302's gateway crash path.
Co-authored-by: tjc0726 <49085201+tjc0726@users.noreply.github.com>