Async REPL conversion with prompt_async | Generated by AI
Here’s the full breakdown of commit f148313:
Title: refactor: convert REPL loop to async with prompt_async
What changed and why:
The iclaw REPL was using session.prompt() (synchronous) from prompt_toolkit. This blocks the thread while waiting for user input, which is fine for simple CLIs but limits future extensibility — e.g., running background tasks, handling async tool callbacks, or integrating with async-native libraries.
iclaw/main.py (2 changes):
- Added
import asyncioat the top. - Split
main()into two functions:main()— thin wrapper that callsasyncio.run(_main())async _main()— the actual REPL logic, now async
- Changed
session.prompt("> ")→await session.prompt_async("> ")
The rest of the REPL logic (tool-call loops, token refresh, command handling) remains synchronous inside the async function — await is only on the prompt call for now. This is a stepping stone: the async shell is in place, and individual operations can be migrated to await incrementally.
tests/test_main.py (2 changes):
-
_mock_session()— updated to mockprompt_asyncinstead ofprompt. Added anasync def _async_side_effect()that yields inputs in order (raising exceptions for error inputs). Keeps.prompt()as a no-op fallback for safety. -
test_token_refresh— the tricky one.asyncio.run()internally callstime.monotonic()for its shutdown/generator cleanup, which consumed entries from theside_effect=[0, 99999, ...]list unpredictably. Replaced the static side_effect list with a callable_monotonic()that tracks call count: returns0for the first 2 calls (asyncio internals + startup), then99999to trigger the token refresh check. This makes the test deterministic regardless of how many internal monotonic calls asyncio makes.
Pre-commit:
- First commit attempt failed — ruff fixed an unused
import asyncioin the test file (it was added butasynciowasn’t directly called in the module scope; the async def uses it implicitly). - Second attempt passed both ruff and ruff-format.
Net effect: +41 lines, -3 lines across 2 files. No behavior change for users — the REPL works exactly the same, just runs under an async event loop now.