codex-agent-framework 0.1.53__tar.gz → 0.1.55__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/CHANGELOG.md +20 -1
  2. {codex_agent_framework-0.1.53/codex_agent_framework.egg-info → codex_agent_framework-0.1.55}/PKG-INFO +3 -1
  3. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/README.md +2 -0
  4. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/agent.py +0 -1
  5. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/agent_runtime.py +16 -6
  6. codex_agent_framework-0.1.55/codex_agent/builtin_commands.py +196 -0
  7. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/client.py +39 -7
  8. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/config.py +154 -67
  9. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/mainloop.py +4 -2
  10. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/server/app.py +2 -1
  11. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/server/core.py +5 -1
  12. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/sessions.py +37 -4
  13. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/tui/state.py +1 -4
  14. codex_agent_framework-0.1.55/codex_agent/version.py +1 -0
  15. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55/codex_agent_framework.egg-info}/PKG-INFO +3 -1
  16. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/pyproject.toml +1 -1
  17. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_audio_hooks.py +7 -7
  18. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_commands.py +56 -13
  19. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_desktop.py +2 -2
  20. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_environment.py +3 -3
  21. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_observe.py +1 -1
  22. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_plugins.py +1 -1
  23. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_providers_config.py +23 -7
  24. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_runtime.py +24 -2
  25. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_tools.py +9 -7
  26. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_chat.py +23 -0
  27. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_cli_headless.py +2 -1
  28. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_client.py +77 -4
  29. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_hooks.py +1 -1
  30. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_server.py +38 -3
  31. codex_agent_framework-0.1.53/codex_agent/builtin_commands.py +0 -152
  32. codex_agent_framework-0.1.53/codex_agent/version.py +0 -1
  33. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/LICENSE +0 -0
  34. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/MANIFEST.in +0 -0
  35. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/__init__.py +0 -0
  36. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/__main__.py +0 -0
  37. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/ai.py +0 -0
  38. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/browser.py +0 -0
  39. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/__init__.py +0 -0
  40. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/bash/__init__.py +0 -0
  41. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/bash/requirements.txt +0 -0
  42. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/browser/__init__.py +0 -0
  43. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/browser/requirements.txt +0 -0
  44. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/content/__init__.py +0 -0
  45. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/content/requirements.txt +0 -0
  46. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/content/system.py +0 -0
  47. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/content/vision.py +0 -0
  48. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/context/__init__.py +0 -0
  49. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/desktop/__init__.py +0 -0
  50. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/desktop/requirements.txt +0 -0
  51. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/environment/__init__.py +0 -0
  52. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/environment/requirements.txt +0 -0
  53. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/files/__init__.py +0 -0
  54. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/files/requirements.txt +0 -0
  55. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/files/tools.py +0 -0
  56. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/image_generation/__init__.py +0 -0
  57. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/image_generation/image_generation_system_prompt.txt +0 -0
  58. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/image_generation/requirements.txt +0 -0
  59. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/interface/__init__.py +0 -0
  60. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/interface/requirements.txt +0 -0
  61. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/memory/__init__.py +0 -0
  62. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/memory/requirements.txt +0 -0
  63. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/planner/__init__.py +0 -0
  64. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/planner/requirements.txt +0 -0
  65. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/python/__init__.py +0 -0
  66. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/python/requirements.txt +0 -0
  67. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/realtime/__init__.py +0 -0
  68. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/realtime/audio.py +0 -0
  69. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/realtime/bridge.py +0 -0
  70. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/realtime/manager.py +0 -0
  71. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/realtime/requirements.txt +0 -0
  72. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/realtime/session.py +0 -0
  73. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/scheduler/__init__.py +0 -0
  74. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/scheduler/requirements.txt +0 -0
  75. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/subagents/__init__.py +0 -0
  76. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/subagents/profiles/explorer.json +0 -0
  77. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/subagents/requirements.txt +0 -0
  78. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/tts/__init__.py +0 -0
  79. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/tts/requirements.txt +0 -0
  80. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/builtin_plugins/web_search/__init__.py +0 -0
  81. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/cli/__init__.py +0 -0
  82. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/cli/headless.py +0 -0
  83. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/cli/main.py +0 -0
  84. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/cli/output.py +0 -0
  85. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/cli/root.py +0 -0
  86. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/cli/runner.py +0 -0
  87. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/command.py +0 -0
  88. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/context.py +0 -0
  89. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/desktop.py +0 -0
  90. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/event.py +0 -0
  91. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/get_text/__init__.py +0 -0
  92. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/get_text/default_gitignore +0 -0
  93. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/get_text/get_text.py +0 -0
  94. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/get_text/simpler_get_text.py +0 -0
  95. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/hooks.py +0 -0
  96. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/image.py +0 -0
  97. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/latex.py +0 -0
  98. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/message.py +0 -0
  99. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/plugin.py +0 -0
  100. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/prompts/system_prompt.txt +0 -0
  101. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/provider.py +0 -0
  102. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/runtime.py +0 -0
  103. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/scheduler.py +0 -0
  104. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/scripts/install-system-dependencies.sh +0 -0
  105. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/server/__init__.py +0 -0
  106. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/service.py +0 -0
  107. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/status.py +0 -0
  108. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/stream.py +0 -0
  109. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/stream_utils.py +0 -0
  110. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/tool.py +0 -0
  111. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/tray.py +0 -0
  112. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/tui/__init__.py +0 -0
  113. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/tui/app.py +0 -0
  114. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/tui/chat.py +0 -0
  115. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/tui/lifecycle.py +0 -0
  116. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/tui/log.py +0 -0
  117. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/tui/status_bar.py +0 -0
  118. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/tui/style.py +0 -0
  119. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/tui/transcript.py +0 -0
  120. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/utils.py +0 -0
  121. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent/worker.py +0 -0
  122. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent_framework.egg-info/SOURCES.txt +0 -0
  123. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
  124. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent_framework.egg-info/entry_points.txt +0 -0
  125. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent_framework.egg-info/requires.txt +0 -0
  126. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/codex_agent_framework.egg-info/top_level.txt +0 -0
  127. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/dependencies.txt +0 -0
  128. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/setup.cfg +0 -0
  129. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent.py +0 -0
  130. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_context.py +0 -0
  131. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_image_generation.py +0 -0
  132. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_realtime.py +0 -0
  133. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_response.py +0 -0
  134. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_sessions.py +0 -0
  135. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_subagents.py +0 -0
  136. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_agent_turns.py +0 -0
  137. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_ai.py +0 -0
  138. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_browser.py +0 -0
  139. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_builtin_config.py +0 -0
  140. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_cli.py +0 -0
  141. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_cli_root.py +0 -0
  142. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_events.py +0 -0
  143. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_get_text_browser.py +0 -0
  144. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_image_message.py +0 -0
  145. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_local_desktop.py +0 -0
  146. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_memory.py +0 -0
  147. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_messages.py +0 -0
  148. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_planner.py +0 -0
  149. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_scheduler.py +0 -0
  150. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_service.py +0 -0
  151. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_tui.py +0 -0
  152. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_utils.py +0 -0
  153. {codex_agent_framework-0.1.53 → codex_agent_framework-0.1.55}/tests/test_worker.py +0 -0
@@ -4,10 +4,29 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  This project loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and uses semantic versioning where practical.
6
6
 
7
+ ## [0.1.55] - 2026-05-17
8
+ ### Fixed
9
+ - Decouple SSE event-stream consumption from local client/TUI event handling so slow or blocked UI work no longer stalls event intake during long-running or heavy tool activity.
10
+ - Remove the unsafe TUI bridge fallback that could execute UI callbacks off the Textual thread after `call_from_thread(...)` failures.
11
+
12
+ ### Tests
13
+ - Add regression coverage for slow local handlers and TUI bridge thread-safety, then validate with targeted client/chat tests (88 passed).
14
+
15
+ ## [0.1.54] - 2026-05-17
16
+ ### Changed
17
+ - Add a generic namespaced `/config` command surface so agent and plugin configs can be read, updated, and saved through a uniform dot-notation interface such as `/config agent.input_token_limit 200000`, `/config tts.voice nova`, `/config agent save`, and `/config save`.
18
+ - Keep config persistence explicit for SDK-created agents while still materializing `agent.config.json` for long-lived server sessions at server bootstrap.
19
+ - Simplify config loading by relying on direct `Config.load(...)` with ignored unknown extras instead of carrying legacy config shims.
20
+ - Hide empty plugin config namespaces from `/config` output so only agent config and plugins with actual exposed config values are shown.
21
+ - Align runtime/server/client paths and tests with the namespaced config model and the minimal default plugin surface.
22
+
23
+ ### Tests
24
+ - Validate with targeted config/server/runtime coverage and the full suite at 611 passed.
25
+
7
26
  ## [0.1.53] - 2026-05-16
8
27
  ### Changed
9
28
  - Default new agents to a smaller built-in plugin surface: `files`, `environment`, `memory`, `planner`, `scheduler`, and `context`.
10
- - Create `~/.codex-agent/agent.config.json` automatically on agent initialization instead of waiting for an explicit config update.
29
+ - Persist `~/.codex-agent/agent.config.json` only on explicit save/update actions instead of automatically on agent initialization.
11
30
  - Make `web_search` and `image_generation` plugin-first server tools: loading the plugin now controls whether the corresponding Responses API tool is exposed.
12
31
  - Remove the redundant `web_search_enabled` and `image_generation_enabled` config flags from the public agent config surface.
13
32
  - Update docs and tests to reflect the minimal default plugin set and plugin-driven server tool activation.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-agent-framework
3
- Version: 0.1.53
3
+ Version: 0.1.55
4
4
  Summary: A lightweight event-driven Codex agent runtime.
5
5
  Author: Baptiste
6
6
  License-Expression: MIT
@@ -129,6 +129,8 @@ turn = stream.turn
129
129
  print("\ncompleted:", turn.completed)
130
130
  ```
131
131
 
132
+ The built-in TUI uses the same streamed event model and is designed to stay responsive during heavier tool activity by decoupling event intake from UI rendering.
133
+
132
134
  Add a small tool:
133
135
 
134
136
  ```python
@@ -80,6 +80,8 @@ turn = stream.turn
80
80
  print("\ncompleted:", turn.completed)
81
81
  ```
82
82
 
83
+ The built-in TUI uses the same streamed event model and is designed to stay responsive during heavier tool activity by decoupling event intake from UI rendering.
84
+
83
85
  Add a small tool:
84
86
 
85
87
  ```python
@@ -54,7 +54,6 @@ class Agent:
54
54
  self.config_manager.update(kwargs, save=False)
55
55
  self.init_working_directories(root_dir=explicit_root_dir or launch_cwd, cwd=explicit_cwd)
56
56
  self.config_manager.apply_openai_capability_defaults()
57
- self.config_manager.save()
58
57
  self.events = EventBus()
59
58
  self.tools_manager = ToolsManager(self)
60
59
  self.providers_manager = ProvidersManager(self)
@@ -189,8 +189,8 @@ class InProcessAgentRuntime(AgentRuntime):
189
189
  def config(self):
190
190
  return self.agent.config
191
191
 
192
- def update_config(self, values=None, save=True, **kwargs):
193
- return self.agent.config_manager.update(values, save=save, **kwargs)
192
+ def update_config(self, values=None, save=True, namespace="agent", **kwargs):
193
+ return self.agent.config_manager.update_config(namespace, values, save=save, **kwargs)
194
194
 
195
195
  def session(self):
196
196
  return self.agent.session
@@ -588,11 +588,16 @@ class ProcessAgentRuntime(AgentRuntime):
588
588
  def config(self):
589
589
  return self._cached_call("config")
590
590
 
591
- def update_config(self, values=None, save=True, **kwargs):
591
+ def update_config(self, values=None, save=True, namespace="agent", **kwargs):
592
592
  payload = dict(values or {})
593
593
  payload.update(kwargs)
594
- result = self.call("update_config", {"values": payload, "save": save}, timeout=self.command_timeout)
595
- self.cache["config"] = result
594
+ result = self.call(
595
+ "update_config",
596
+ {"namespace": namespace, "values": payload, "save": save},
597
+ timeout=self.command_timeout,
598
+ )
599
+ if namespace == "agent":
600
+ self.cache["config"] = result
596
601
  return result
597
602
 
598
603
  def session(self):
@@ -703,6 +708,7 @@ class _RuntimeFuture:
703
708
 
704
709
  def agent_worker_main(command_queue, response_queue, event_queue, agent_kwargs):
705
710
  agent = Agent(**dict(agent_kwargs or {}))
711
+ agent.config_manager.save()
706
712
 
707
713
  def emit_event(event):
708
714
  event_queue.put(event_payload(event))
@@ -749,7 +755,11 @@ def run_worker_command(agent, name, payload):
749
755
  if name == "config":
750
756
  return agent.config
751
757
  if name == "update_config":
752
- return agent.config_manager.update(payload.get("values") or {}, save=payload.get("save", True))
758
+ return agent.config_manager.update_config(
759
+ payload.get("namespace", "agent"),
760
+ payload.get("values") or {},
761
+ save=payload.get("save", True),
762
+ )
753
763
  if name == "session":
754
764
  return agent.session
755
765
  if name == "sessions":
@@ -0,0 +1,196 @@
1
+ """Built-in slash commands."""
2
+
3
+ from modict import modict
4
+
5
+ from .command import command
6
+ from .config import parse_config_value
7
+ from .sessions import AgentSession
8
+ from .tool import get_agent
9
+
10
+
11
+ def _format_lines(value, prefix=""):
12
+ if isinstance(value, dict):
13
+ lines = []
14
+ for key, child in value.items():
15
+ full_key = f"{prefix}.{key}" if prefix else str(key)
16
+ if isinstance(child, dict):
17
+ lines.append(f"{full_key}:")
18
+ lines.extend(_format_lines(child, full_key))
19
+ else:
20
+ lines.append(f"{full_key}: {child!r}")
21
+ return lines
22
+ return [f"{prefix}: {value!r}" if prefix else repr(value)]
23
+
24
+
25
+ def format_config_output(value, prefix=""):
26
+ return "\n".join(_format_lines(value, prefix=prefix)) if isinstance(value, dict) else repr(value)
27
+
28
+
29
+ @command
30
+ def help():
31
+ agent = get_agent()
32
+ names = ", ".join(f"/{name}" for name in sorted(agent.commands))
33
+ return f"Available commands: {names}"
34
+
35
+
36
+ @command
37
+ def compact(max_compacted_turns=None):
38
+ agent = get_agent()
39
+ max_compacted_turns = None if max_compacted_turns in (None, "", "all") else int(max_compacted_turns)
40
+ message = agent.compact_session(
41
+ model=agent.config.model,
42
+ instructions=agent.config_manager.system_message().content,
43
+ max_compacted_turns=max_compacted_turns,
44
+ )
45
+ if message is None:
46
+ return "Nothing to compact."
47
+ if message.get("status") == "failed":
48
+ return f"Compaction failed: {message.error}"
49
+ suffix = "" if max_compacted_turns is None else f" from {max_compacted_turns} compacted turn(s)"
50
+ return f"Compacted {message.compacted_message_count} messages{suffix}."
51
+
52
+
53
+ def _message_count_text(count):
54
+ return f"{count} message" if count == 1 else f"{count} messages"
55
+
56
+
57
+ def _session_infos(agent):
58
+ infos = []
59
+ for index, info in enumerate(AgentSession.list_session_infos(), start=1):
60
+ infos.append(modict(
61
+ index=index,
62
+ session_id=info.session_id,
63
+ title=info.title or info.session_id,
64
+ message_count=info.message_count,
65
+ current=info.session_id == agent.current_session_id,
66
+ ))
67
+ return infos
68
+
69
+
70
+ def _format_session_list(agent):
71
+ infos = _session_infos(agent)
72
+ if not infos:
73
+ return "No saved sessions."
74
+ lines = ["Sessions:"]
75
+ for info in infos:
76
+ marker = "*" if info.current else " "
77
+ lines.append(
78
+ f"{marker} [{info.index}] {info.title} "
79
+ f"({info.session_id}, {_message_count_text(info.message_count)})"
80
+ )
81
+ return "\n".join(lines)
82
+
83
+
84
+ @command
85
+ def session(action=None, *parts):
86
+ agent = get_agent()
87
+ if action is None:
88
+ return _format_session_list(agent)
89
+
90
+ action_text = str(action).strip()
91
+ action_lower = action_text.lower()
92
+ if action_lower == "new":
93
+ created = agent.start_new_session()
94
+ return f"Created session: {created.session_id}"
95
+
96
+ if action_lower == "rename":
97
+ title = " ".join(str(part) for part in parts).strip()
98
+ if not title:
99
+ raise ValueError("/session rename requires a title")
100
+ if agent.session is None:
101
+ raise ValueError("No active session to rename")
102
+ agent.session.title = title
103
+ agent.save_session()
104
+ return f"Renamed session: {title}"
105
+
106
+ if parts:
107
+ raise ValueError("Usage: /session, /session <index>, /session new, /session rename <title>")
108
+
109
+ try:
110
+ index = int(action_text)
111
+ except ValueError as exc:
112
+ raise ValueError(f"Unknown /session action: {action_text}") from exc
113
+
114
+ infos = _session_infos(agent)
115
+ if index < 1 or index > len(infos):
116
+ raise ValueError(f"Session index out of range: {index}")
117
+ loaded = agent.load_session(infos[index - 1].session_id)
118
+ return f"Loaded session: {loaded.session_id}"
119
+
120
+
121
+ @command
122
+ def config(path=None, value=None, *extra, **values):
123
+ agent = get_agent()
124
+ manager = agent.config_manager
125
+
126
+ if values:
127
+ unknown = sorted(key for key in values if key not in agent.config)
128
+ if unknown:
129
+ raise ValueError(f"Unknown model config keys: {', '.join(unknown)}")
130
+ parsed = {key: parse_config_value(raw) for key, raw in values.items()}
131
+ manager.update_config("agent", config=parsed, save=True)
132
+ changed = ", ".join(f"{key}={agent.config.get(key)!r}" for key in parsed)
133
+ return f"Updated config: {changed}" if changed else format_config_output(manager.export_tree())
134
+
135
+ if path is None:
136
+ if value is not None or extra:
137
+ raise ValueError("/config with no path does not accept extra arguments")
138
+ return format_config_output(manager.export_tree())
139
+
140
+ if path == "save":
141
+ manager.save_all()
142
+ return "Saved all config namespaces."
143
+
144
+ if value is None and not extra:
145
+ displayed = manager.get_path(path)
146
+ return format_config_output(displayed, prefix=path)
147
+
148
+ if value == "save" and not extra:
149
+ namespace, remainder = manager.namespace_path(path)
150
+ if remainder:
151
+ raise ValueError("Save targets a config namespace, not a nested key")
152
+ manager.save_config(namespace)
153
+ return f"Saved config namespace: {namespace}"
154
+
155
+ raw_value = " ".join(str(part) for part in ((value,) + extra))
156
+ updated = manager.set_path(path, raw_value, save=False)
157
+ return f"Updated config: {path}={updated!r}"
158
+
159
+
160
+ @command
161
+ def model(value=None):
162
+ if value is None:
163
+ return f"model: {get_agent().config.model!r}"
164
+ get_agent().config_manager.set_path("agent.model", value, save=True)
165
+ return f"Updated config: model={get_agent().config.model!r}"
166
+
167
+
168
+ @command
169
+ def reasoning(value=None):
170
+ agent = get_agent()
171
+ if value is None:
172
+ return f"reasoning: {agent.config.get('reasoning')!r}"
173
+ current = dict(agent.config.get("reasoning") or {})
174
+ current["effort"] = value
175
+ current.setdefault("summary", "auto")
176
+ agent.config_manager.update_config("agent", reasoning=current, save=True)
177
+ return f"Updated config: reasoning={agent.config.get('reasoning')!r}"
178
+
179
+
180
+ @command
181
+ def verbosity(value=None):
182
+ agent = get_agent()
183
+ if value is None:
184
+ return f"text: {agent.config.get('text')!r}"
185
+ current = dict(agent.config.get("text") or {})
186
+ current["verbosity"] = value
187
+ agent.config_manager.update_config("agent", text=current, save=True)
188
+ return f"Updated config: text={agent.config.get('text')!r}"
189
+
190
+
191
+ @command
192
+ def voice(value=None):
193
+ agent = get_agent()
194
+ if "tts" not in agent.plugins:
195
+ agent.load_builtin_plugin("tts")
196
+ return agent.plugins.tts.voice(value)
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import os
3
+ import queue
3
4
  import threading
4
5
  import time
5
6
  from urllib.parse import urlencode
@@ -22,7 +23,9 @@ class AgentClient:
22
23
  self._event_handlers = {}
23
24
  self._event_thread = None
24
25
  self._health_thread = None
26
+ self._dispatch_thread = None
25
27
  self._event_stop = threading.Event()
28
+ self._local_event_queue = queue.Queue()
26
29
  self._server_connected = False
27
30
  self._event_stream_connected = False
28
31
  self._event_stream_response = None
@@ -77,10 +80,35 @@ class AgentClient:
77
80
  self.emit_local({"type": "client_event_error", "error": str(exc), "event": event_type})
78
81
  return event
79
82
 
83
+ def _start_dispatch_thread(self):
84
+ if self._dispatch_thread and self._dispatch_thread.is_alive():
85
+ return self
86
+ self._dispatch_thread = threading.Thread(target=self._dispatch_loop, name="agent-client-dispatch", daemon=True)
87
+ self._dispatch_thread.start()
88
+ return self
89
+
90
+ def _enqueue_local(self, event):
91
+ self._start_dispatch_thread()
92
+ self._local_event_queue.put(modict(event))
93
+ return event
94
+
95
+ def _dispatch_loop(self):
96
+ while True:
97
+ try:
98
+ event = self._local_event_queue.get(timeout=0.1)
99
+ except queue.Empty:
100
+ if self._event_stop.is_set():
101
+ break
102
+ continue
103
+ if event is None:
104
+ break
105
+ self.emit_local(event)
106
+
80
107
  def start_events(self):
81
108
  if self._event_thread and self._event_thread.is_alive():
82
109
  return self
83
110
  self._event_stop.clear()
111
+ self._start_dispatch_thread()
84
112
  self._event_thread = threading.Thread(target=self._event_loop, name="agent-client-events", daemon=True)
85
113
  self._health_thread = threading.Thread(target=self._health_loop, name="agent-client-health", daemon=True)
86
114
  self._event_thread.start()
@@ -89,10 +117,13 @@ class AgentClient:
89
117
 
90
118
  def stop_events(self, timeout=0.2):
91
119
  self._event_stop.set()
120
+ self._local_event_queue.put(None)
92
121
  if self._event_thread:
93
122
  self._event_thread.join(timeout)
94
123
  if self._health_thread:
95
124
  self._health_thread.join(timeout)
125
+ if self._dispatch_thread:
126
+ self._dispatch_thread.join(timeout)
96
127
  return self
97
128
 
98
129
  def _set_server_connected(self):
@@ -100,7 +131,7 @@ class AgentClient:
100
131
  if self._server_connected:
101
132
  return False
102
133
  self._server_connected = True
103
- self.emit_local({"type": "server_connected"})
134
+ self._enqueue_local({"type": "server_connected"})
104
135
  return True
105
136
 
106
137
  def _set_server_offline(self, error=""):
@@ -108,7 +139,7 @@ class AgentClient:
108
139
  if not self._server_connected:
109
140
  return False
110
141
  self._server_connected = False
111
- self.emit_local({"type": "server_offline", "error": str(error or "")})
142
+ self._enqueue_local({"type": "server_offline", "error": str(error or "")})
112
143
  return True
113
144
 
114
145
  def _set_event_stream_connected(self, connected):
@@ -180,6 +211,7 @@ class AgentClient:
180
211
  self._event_stop.wait(self.health_interval)
181
212
 
182
213
  def _event_loop(self):
214
+ self._start_dispatch_thread()
183
215
  while not self._event_stop.is_set():
184
216
  try:
185
217
  with requests.get(
@@ -200,7 +232,7 @@ class AgentClient:
200
232
  break
201
233
  self._record_event_stream_activity()
202
234
  self.remember_event_sequence(event)
203
- self.emit_local(event)
235
+ self._enqueue_local(event)
204
236
  finally:
205
237
  self._clear_event_stream_response(response)
206
238
  if stream_ended and not self._event_stop.is_set():
@@ -214,7 +246,7 @@ class AgentClient:
214
246
  reconnecting = self._consume_event_reconnect_requested()
215
247
  if not (reconnecting or (self._server_connected and self.is_event_idle_timeout(exc))):
216
248
  self._set_server_offline(str(exc))
217
- self.emit_local(self.event_error_payload(exc))
249
+ self._enqueue_local(self.event_error_payload(exc))
218
250
  self._event_stop.wait(0.1)
219
251
 
220
252
  def event_error_payload(self, exc):
@@ -327,10 +359,10 @@ class AgentClient:
327
359
  def get_config(self):
328
360
  return self.get("/config")
329
361
 
330
- def update_config(self, values=None, save=True, **kwargs):
362
+ def update_config(self, values=None, save=True, namespace="agent", **kwargs):
331
363
  payload = dict(values or {})
332
364
  payload.update(kwargs)
333
- return self.request("PATCH", "/config", json={"values": payload, "save": save})
365
+ return self.request("PATCH", "/config", json={"values": payload, "save": save, "namespace": namespace})
334
366
 
335
367
  def tools(self):
336
368
  return self.get("/tools")
@@ -368,7 +400,7 @@ class AgentClient:
368
400
  return self.post("/interrupt", {"reason": reason})
369
401
  except Exception as exc:
370
402
  payload = modict(interrupted=False, interrupt_requested=False, error=str(exc))
371
- self.emit_local({
403
+ self._enqueue_local({
372
404
  "type": "client_event_error",
373
405
  "error": str(exc),
374
406
  "source": "interrupt",