codex-agent-framework 0.1.52__tar.gz → 0.1.54__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.52 → codex_agent_framework-0.1.54}/CHANGELOG.md +22 -0
  2. {codex_agent_framework-0.1.52/codex_agent_framework.egg-info → codex_agent_framework-0.1.54}/PKG-INFO +3 -2
  3. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/README.md +2 -1
  4. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/agent_runtime.py +16 -6
  5. codex_agent_framework-0.1.54/codex_agent/builtin_commands.py +196 -0
  6. codex_agent_framework-0.1.54/codex_agent/builtin_plugins/web_search/__init__.py +11 -0
  7. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/client.py +2 -2
  8. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/config.py +155 -70
  9. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/mainloop.py +4 -2
  10. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/server/app.py +2 -1
  11. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/server/core.py +5 -1
  12. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/sessions.py +37 -4
  13. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tool.py +1 -2
  14. codex_agent_framework-0.1.54/codex_agent/version.py +1 -0
  15. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54/codex_agent_framework.egg-info}/PKG-INFO +3 -2
  16. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent_framework.egg-info/SOURCES.txt +1 -0
  17. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/pyproject.toml +1 -1
  18. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent.py +16 -24
  19. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_audio_hooks.py +7 -7
  20. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_commands.py +56 -13
  21. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_desktop.py +2 -2
  22. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_environment.py +3 -3
  23. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_image_generation.py +6 -4
  24. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_observe.py +1 -1
  25. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_plugins.py +16 -22
  26. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_providers_config.py +34 -6
  27. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_response.py +14 -11
  28. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_runtime.py +24 -2
  29. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_tools.py +9 -7
  30. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_cli_headless.py +2 -1
  31. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_hooks.py +1 -1
  32. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_server.py +38 -3
  33. codex_agent_framework-0.1.52/codex_agent/builtin_commands.py +0 -152
  34. codex_agent_framework-0.1.52/codex_agent/version.py +0 -1
  35. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/LICENSE +0 -0
  36. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/MANIFEST.in +0 -0
  37. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/__init__.py +0 -0
  38. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/__main__.py +0 -0
  39. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/agent.py +0 -0
  40. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/ai.py +0 -0
  41. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/browser.py +0 -0
  42. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/__init__.py +0 -0
  43. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/bash/__init__.py +0 -0
  44. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/bash/requirements.txt +0 -0
  45. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/browser/__init__.py +0 -0
  46. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/browser/requirements.txt +0 -0
  47. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/content/__init__.py +0 -0
  48. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/content/requirements.txt +0 -0
  49. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/content/system.py +0 -0
  50. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/content/vision.py +0 -0
  51. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/context/__init__.py +0 -0
  52. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/desktop/__init__.py +0 -0
  53. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/desktop/requirements.txt +0 -0
  54. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/environment/__init__.py +0 -0
  55. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/environment/requirements.txt +0 -0
  56. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/files/__init__.py +0 -0
  57. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/files/requirements.txt +0 -0
  58. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/files/tools.py +0 -0
  59. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/image_generation/__init__.py +0 -0
  60. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/image_generation/image_generation_system_prompt.txt +0 -0
  61. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/image_generation/requirements.txt +0 -0
  62. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/interface/__init__.py +0 -0
  63. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/interface/requirements.txt +0 -0
  64. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/memory/__init__.py +0 -0
  65. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/memory/requirements.txt +0 -0
  66. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/planner/__init__.py +0 -0
  67. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/planner/requirements.txt +0 -0
  68. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/python/__init__.py +0 -0
  69. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/python/requirements.txt +0 -0
  70. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/realtime/__init__.py +0 -0
  71. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/realtime/audio.py +0 -0
  72. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/realtime/bridge.py +0 -0
  73. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/realtime/manager.py +0 -0
  74. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/realtime/requirements.txt +0 -0
  75. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/realtime/session.py +0 -0
  76. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/scheduler/__init__.py +0 -0
  77. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/scheduler/requirements.txt +0 -0
  78. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/subagents/__init__.py +0 -0
  79. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/subagents/profiles/explorer.json +0 -0
  80. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/subagents/requirements.txt +0 -0
  81. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/tts/__init__.py +0 -0
  82. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/tts/requirements.txt +0 -0
  83. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/cli/__init__.py +0 -0
  84. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/cli/headless.py +0 -0
  85. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/cli/main.py +0 -0
  86. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/cli/output.py +0 -0
  87. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/cli/root.py +0 -0
  88. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/cli/runner.py +0 -0
  89. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/command.py +0 -0
  90. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/context.py +0 -0
  91. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/desktop.py +0 -0
  92. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/event.py +0 -0
  93. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/get_text/__init__.py +0 -0
  94. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/get_text/default_gitignore +0 -0
  95. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/get_text/get_text.py +0 -0
  96. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/get_text/simpler_get_text.py +0 -0
  97. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/hooks.py +0 -0
  98. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/image.py +0 -0
  99. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/latex.py +0 -0
  100. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/message.py +0 -0
  101. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/plugin.py +0 -0
  102. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/prompts/system_prompt.txt +0 -0
  103. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/provider.py +0 -0
  104. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/runtime.py +0 -0
  105. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/scheduler.py +0 -0
  106. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/scripts/install-system-dependencies.sh +0 -0
  107. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/server/__init__.py +0 -0
  108. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/service.py +0 -0
  109. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/status.py +0 -0
  110. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/stream.py +0 -0
  111. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/stream_utils.py +0 -0
  112. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tray.py +0 -0
  113. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/__init__.py +0 -0
  114. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/app.py +0 -0
  115. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/chat.py +0 -0
  116. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/lifecycle.py +0 -0
  117. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/log.py +0 -0
  118. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/state.py +0 -0
  119. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/status_bar.py +0 -0
  120. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/style.py +0 -0
  121. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/transcript.py +0 -0
  122. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/utils.py +0 -0
  123. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/worker.py +0 -0
  124. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
  125. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent_framework.egg-info/entry_points.txt +0 -0
  126. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent_framework.egg-info/requires.txt +0 -0
  127. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent_framework.egg-info/top_level.txt +0 -0
  128. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/dependencies.txt +0 -0
  129. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/setup.cfg +0 -0
  130. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_context.py +0 -0
  131. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_realtime.py +0 -0
  132. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_sessions.py +0 -0
  133. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_subagents.py +0 -0
  134. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_turns.py +0 -0
  135. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_ai.py +0 -0
  136. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_browser.py +0 -0
  137. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_builtin_config.py +0 -0
  138. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_chat.py +0 -0
  139. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_cli.py +0 -0
  140. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_cli_root.py +0 -0
  141. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_client.py +0 -0
  142. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_events.py +0 -0
  143. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_get_text_browser.py +0 -0
  144. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_image_message.py +0 -0
  145. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_local_desktop.py +0 -0
  146. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_memory.py +0 -0
  147. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_messages.py +0 -0
  148. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_planner.py +0 -0
  149. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_scheduler.py +0 -0
  150. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_service.py +0 -0
  151. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_tui.py +0 -0
  152. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_utils.py +0 -0
  153. {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_worker.py +0 -0
@@ -4,6 +4,28 @@ 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.54] - 2026-05-17
8
+ ### Changed
9
+ - 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`.
10
+ - Keep config persistence explicit for SDK-created agents while still materializing `agent.config.json` for long-lived server sessions at server bootstrap.
11
+ - Simplify config loading by relying on direct `Config.load(...)` with ignored unknown extras instead of carrying legacy config shims.
12
+ - Hide empty plugin config namespaces from `/config` output so only agent config and plugins with actual exposed config values are shown.
13
+ - Align runtime/server/client paths and tests with the namespaced config model and the minimal default plugin surface.
14
+
15
+ ### Tests
16
+ - Validate with targeted config/server/runtime coverage and the full suite at 611 passed.
17
+
18
+ ## [0.1.53] - 2026-05-16
19
+ ### Changed
20
+ - Default new agents to a smaller built-in plugin surface: `files`, `environment`, `memory`, `planner`, `scheduler`, and `context`.
21
+ - Persist `~/.codex-agent/agent.config.json` only on explicit save/update actions instead of automatically on agent initialization.
22
+ - Make `web_search` and `image_generation` plugin-first server tools: loading the plugin now controls whether the corresponding Responses API tool is exposed.
23
+ - Remove the redundant `web_search_enabled` and `image_generation_enabled` config flags from the public agent config surface.
24
+ - Update docs and tests to reflect the minimal default plugin set and plugin-driven server tool activation.
25
+
26
+ ### Tests
27
+ - Validate with targeted agent/plugin/image-generation coverage and a package build.
28
+
7
29
  ## [0.1.52] - 2026-05-16
8
30
  ### Changed
9
31
  - Document current synchronous Bash tool behavior and the planned direction for pollable, cancellable long-running shell jobs with durable logs and clear UI/API status.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-agent-framework
3
- Version: 0.1.52
3
+ Version: 0.1.54
4
4
  Summary: A lightweight event-driven Codex agent runtime.
5
5
  Author: Baptiste
6
6
  License-Expression: MIT
@@ -188,7 +188,8 @@ agent = Agent(
188
188
  )
189
189
  ```
190
190
 
191
- `None` loads all built-ins, `[]` loads none, and an explicit list selects plugin module names.
191
+ By default, the agent starts with a minimal built-in surface: `files`, `environment`, `memory`, `planner`, `scheduler`, and `context`.
192
+ Pass `builtin_plugins=None` to load all built-ins, `[]` to load none, or an explicit list to select plugin module names.
192
193
 
193
194
  ## Project health
194
195
 
@@ -139,7 +139,8 @@ agent = Agent(
139
139
  )
140
140
  ```
141
141
 
142
- `None` loads all built-ins, `[]` loads none, and an explicit list selects plugin module names.
142
+ By default, the agent starts with a minimal built-in surface: `files`, `environment`, `memory`, `planner`, `scheduler`, and `context`.
143
+ Pass `builtin_plugins=None` to load all built-ins, `[]` to load none, or an explicit list to select plugin module names.
143
144
 
144
145
  ## Project health
145
146
 
@@ -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)
@@ -0,0 +1,11 @@
1
+ from ...plugin import Plugin
2
+ from ...tool import WebSearchTool
3
+
4
+
5
+ class WebSearchPlugin(Plugin):
6
+ name = "web_search"
7
+ description = "OpenAI Responses API web search server tool."
8
+
9
+ def __init__(self, agent):
10
+ super().__init__(agent)
11
+ self.agent.tools_manager.add_server_tool(WebSearchTool())
@@ -327,10 +327,10 @@ class AgentClient:
327
327
  def get_config(self):
328
328
  return self.get("/config")
329
329
 
330
- def update_config(self, values=None, save=True, **kwargs):
330
+ def update_config(self, values=None, save=True, namespace="agent", **kwargs):
331
331
  payload = dict(values or {})
332
332
  payload.update(kwargs)
333
- return self.request("PATCH", "/config", json={"values": payload, "save": save})
333
+ return self.request("PATCH", "/config", json={"values": payload, "save": save, "namespace": namespace})
334
334
 
335
335
  def tools(self):
336
336
  return self.get("/tools")
@@ -1,7 +1,9 @@
1
+ import json
1
2
  import os
2
3
  import platform
3
4
 
4
5
  from modict import modict
6
+ from modict.collections_utils import get_nested
5
7
 
6
8
  from .event import RuntimeStateChangedEvent
7
9
  from .message import SystemMessage
@@ -12,8 +14,71 @@ DEFAULT_SYSTEM_PROMPT_PATH = os.path.join(os.path.dirname(__file__), "prompts",
12
14
  DEFAULT_SYSTEM_PROMPT = text_content(DEFAULT_SYSTEM_PROMPT_PATH)
13
15
 
14
16
 
15
- class AgentConfig(modict):
16
- _config = modict.config(enforce_json=True)
17
+ def parse_config_value(value):
18
+ if isinstance(value, bool):
19
+ return value
20
+ if not isinstance(value, str):
21
+ return value
22
+
23
+ lowered = value.lower()
24
+ if lowered in ("true", "yes", "on", "1"):
25
+ return True
26
+ if lowered in ("false", "no", "off", "0"):
27
+ return False
28
+ if lowered in ("none", "null"):
29
+ return None
30
+ if value.isdigit() or (value.startswith("-") and value[1:].isdigit()):
31
+ return int(value)
32
+ try:
33
+ if any(char in value for char in (".", "e", "E")):
34
+ return float(value)
35
+ except ValueError:
36
+ pass
37
+ if (value.startswith("{") and value.endswith("}")) or (value.startswith("[") and value.endswith("]")):
38
+ return json.loads(value)
39
+ return value
40
+
41
+
42
+ class Config(modict):
43
+ _config = modict.config(enforce_json=True, extra="ignore", validate_default=True)
44
+
45
+ def save(self, file_path):
46
+ target = str(file_path)
47
+ if not target:
48
+ raise ValueError(f"No file path configured for {type(self).__name__}")
49
+ parent = os.path.dirname(target)
50
+ if parent:
51
+ os.makedirs(parent, exist_ok=True)
52
+ self.dump(target, indent=2, ensure_ascii=False)
53
+ return self
54
+
55
+ def set_path(self, path, raw_value):
56
+ path = str(path or "").strip()
57
+ value = parse_config_value(raw_value)
58
+ if not path:
59
+ if not isinstance(value, dict):
60
+ raise ValueError("Root config assignment expects a mapping value")
61
+ self.clear()
62
+ self.update(value)
63
+ return self
64
+ if "." not in path:
65
+ self[path] = value
66
+ return self[path]
67
+ parent_path, _, leaf = path.rpartition(".")
68
+ parent = get_nested(self, parent_path)
69
+ if not isinstance(parent, dict):
70
+ raise TypeError(f"Config path {path!r} does not resolve to a mapping parent")
71
+ parent[leaf] = value
72
+ return get_nested(self, path)
73
+
74
+ def get_path(self, path=None, default=None):
75
+ path = str(path or "").strip()
76
+ if not path:
77
+ return self
78
+ return get_nested(self, path, default=default)
79
+
80
+
81
+ class AgentConfig(Config):
17
82
 
18
83
  model = "gpt-5.4"
19
84
  system = DEFAULT_SYSTEM_PROMPT
@@ -35,14 +100,12 @@ class AgentConfig(modict):
35
100
  vision_enabled = True
36
101
  max_images = 10
37
102
  prune_orphaned_tool_outputs_on_context_build = True
38
- web_search_enabled = False
39
- image_generation_enabled = False
40
103
  image_generation_output_format = "png"
41
104
  image_generation_size = None
42
105
  image_generation_quality = None
43
106
  image_generation_background = None
44
107
  image_generation_moderation = None
45
- builtin_plugins = None
108
+ builtin_plugins = ["files", "environment", "memory", "planner", "scheduler", "context"]
46
109
  custom_plugins = None
47
110
  exclude_tools = modict.factory(list)
48
111
  server_url = "http://127.0.0.1:8765"
@@ -67,8 +130,9 @@ class AgentConfig(modict):
67
130
  )
68
131
 
69
132
 
70
- class PluginConfig(modict):
71
- _config = modict.config(enforce_json=True)
133
+
134
+ class PluginConfig(Config):
135
+ _config = modict.config(enforce_json=True, extra="allow", validate_default=True)
72
136
 
73
137
 
74
138
  def _as_name_set(value):
@@ -94,10 +158,6 @@ class ConfigManager:
94
158
  def file(self):
95
159
  return runtime_join("agent.config.json")
96
160
 
97
- @property
98
- def legacy_file(self):
99
- return runtime_join("agent_config.json")
100
-
101
161
  def config_file(self, namespace="agent", file=None):
102
162
  if file is not None:
103
163
  return str(file)
@@ -108,13 +168,22 @@ class ConfigManager:
108
168
  def save(self):
109
169
  return self.save_config("agent")
110
170
 
171
+ def save_all(self):
172
+ if self.should_queue_state_command():
173
+ return self.agent.submit("save_config", namespace=None).wait().result
174
+ saved = modict()
175
+ for namespace in list(self.configs):
176
+ saved[namespace] = self.save_config(namespace)
177
+ return saved
178
+
111
179
  def save_config(self, namespace="agent"):
112
- if namespace == "agent" and self.should_queue_state_command():
113
- return self.agent.submit("save_config").wait().result
114
- config = self.configs.get(namespace)
115
- if config is None:
116
- raise KeyError(f"Unknown config namespace: {namespace}")
180
+ if self.should_queue_state_command():
181
+ return self.agent.submit("save_config", namespace=namespace).wait().result
182
+ config = self.get_config(namespace)
117
183
  file = self.config_files.get(namespace) or self.config_file(namespace)
184
+ self.config_files[namespace] = file
185
+ if isinstance(config, Config):
186
+ return config.save(file)
118
187
  parent = os.path.dirname(file)
119
188
  if parent:
120
189
  os.makedirs(parent, exist_ok=True)
@@ -122,9 +191,8 @@ class ConfigManager:
122
191
  return config
123
192
 
124
193
  def load(self):
125
- source = self.file if os.path.isfile(self.file) else self.legacy_file
126
- if os.path.isfile(source):
127
- self.config = AgentConfig.load(source)
194
+ if os.path.isfile(self.file):
195
+ self.config = AgentConfig.load(self.file)
128
196
  self.agent.config = self.config
129
197
  self.configs.agent = self.config
130
198
  self.config_files.agent = self.file
@@ -144,38 +212,24 @@ class ConfigManager:
144
212
  return config
145
213
 
146
214
  def update(self, config=None, save=True, **kwargs):
215
+ return self.update_config("agent", config=config, save=save, **kwargs)
216
+
217
+ def update_config(self, namespace="agent", config=None, save=True, **kwargs):
147
218
  if self.should_queue_state_command():
148
219
  return self.agent.submit(
149
220
  "update_config",
221
+ namespace=namespace,
150
222
  config=config,
151
223
  save=save,
152
224
  kwargs=kwargs,
153
225
  ).wait().result
154
- data = modict(config or {})
155
- data.update(kwargs)
156
- if data:
157
- self.config.update(data)
158
- self.agent.config = self.config
159
- if save:
160
- self.save()
161
- self.emit_state_changed(
162
- reason="config_updated",
163
- namespace="config",
164
- name="agent",
165
- details=data,
166
- )
167
- return self.config
168
-
169
- def update_config(self, namespace="agent", config=None, save=True, **kwargs):
170
- if namespace == "agent":
171
- return self.update(config, save=save, **kwargs)
172
- target = self.configs.get(namespace)
173
- if target is None:
174
- raise KeyError(f"Unknown config namespace: {namespace}")
226
+ target = self.get_config(namespace)
175
227
  data = modict(config or {})
176
228
  data.update(kwargs)
177
229
  if data:
178
230
  target.update(data)
231
+ if namespace == "agent":
232
+ self.agent.config = target
179
233
  if save:
180
234
  self.save_config(namespace)
181
235
  self.emit_state_changed(
@@ -186,6 +240,67 @@ class ConfigManager:
186
240
  )
187
241
  return target
188
242
 
243
+ def get_config(self, namespace="agent"):
244
+ target = self.configs.get(str(namespace))
245
+ if target is None:
246
+ raise KeyError(f"Unknown config namespace: {namespace}")
247
+ return target
248
+
249
+ def namespace_path(self, path=None):
250
+ text = str(path or "").strip()
251
+ if not text:
252
+ return None, ""
253
+ namespace, _, remainder = text.partition(".")
254
+ if namespace not in self.configs:
255
+ raise KeyError(f"Unknown config namespace: {namespace}")
256
+ return namespace, remainder
257
+
258
+ def get_path(self, path=None, default=None):
259
+ if not path:
260
+ return modict({namespace: config for namespace, config in self.configs.items()})
261
+ namespace, remainder = self.namespace_path(path)
262
+ target = self.get_config(namespace)
263
+ return target.get_path(remainder, default=default) if isinstance(target, Config) else get_nested(target, remainder, default=default)
264
+
265
+ def set_path(self, path, raw_value, *, save=False):
266
+ namespace, remainder = self.namespace_path(path)
267
+ target = self.get_config(namespace)
268
+ if isinstance(target, Config):
269
+ result = target.set_path(remainder, raw_value)
270
+ else:
271
+ parsed = parse_config_value(raw_value)
272
+ if not remainder:
273
+ if not isinstance(parsed, dict):
274
+ raise ValueError("Root config assignment expects a mapping value")
275
+ target.clear()
276
+ target.update(parsed)
277
+ result = target
278
+ else:
279
+ parent_path, _, leaf = remainder.rpartition(".")
280
+ parent = target if not parent_path else get_nested(target, parent_path)
281
+ if not isinstance(parent, dict):
282
+ raise TypeError(f"Config path {path!r} does not resolve to a mapping parent")
283
+ parent[leaf] = parsed
284
+ result = get_nested(target, remainder)
285
+ if namespace == "agent":
286
+ self.agent.config = self.config
287
+ if save:
288
+ self.save_config(namespace)
289
+ self.emit_state_changed(
290
+ reason="config_updated",
291
+ namespace="config",
292
+ name=namespace,
293
+ details=modict(path=path, value=self.get_path(path)),
294
+ )
295
+ return result
296
+
297
+ def export_tree(self):
298
+ return modict({
299
+ namespace: config
300
+ for namespace, config in self.configs.items()
301
+ if namespace == "agent" or bool(config)
302
+ })
303
+
189
304
  def emit_state_changed(self, **data):
190
305
  if hasattr(self.agent, "events"):
191
306
  self.agent.emit(RuntimeStateChangedEvent, **data)
@@ -223,22 +338,6 @@ class ConfigManager:
223
338
  self.config.pop(key, None)
224
339
  return self.config
225
340
 
226
- def pop_legacy_section(self, section):
227
- parts = str(section).split(".")
228
- parent = self.config
229
- for part in parts[:-1]:
230
- parent = parent.get(part) if isinstance(parent, dict) else None
231
- if parent is None:
232
- return None
233
- if isinstance(parent, dict):
234
- return parent.pop(parts[-1], None)
235
- return None
236
-
237
- def prune_empty_legacy_containers(self):
238
- plugins = self.config.get("plugins")
239
- if isinstance(plugins, dict) and not plugins:
240
- self.config.pop("plugins", None)
241
-
242
341
  def apply_config_defaults(
243
342
  self,
244
343
  namespace,
@@ -250,21 +349,7 @@ class ConfigManager:
250
349
  legacy_agent_sections=None,
251
350
  ):
252
351
  config = self.load_config(namespace, config_class=config_class, file=file, defaults=defaults)
253
- migrated = False
254
- for section in legacy_agent_sections or ():
255
- legacy = self.pop_legacy_section(section)
256
- if isinstance(legacy, dict):
257
- config.update(legacy)
258
- migrated = True
259
- for legacy_key, plugin_key in (legacy_agent_aliases or {}).items():
260
- if legacy_key in self.config:
261
- config[plugin_key] = self.config.pop(legacy_key)
262
- migrated = True
263
- self.prune_empty_legacy_containers()
264
352
  self.agent.config = self.config
265
- if migrated:
266
- self.save_config(namespace)
267
- self.save()
268
353
  return config
269
354
 
270
355
  def system_message(self):
@@ -263,13 +263,15 @@ class MainLoopManager:
263
263
  if command.name == "navigate_session":
264
264
  return self.agent.navigate_session(command.payload.direction)
265
265
  if command.name == "update_config":
266
- return self.agent.config_manager.update(
266
+ return self.agent.config_manager.update_config(
267
+ command.payload.get("namespace", "agent"),
267
268
  command.payload.get("config"),
268
269
  save=command.payload.get("save", True),
269
270
  **command.payload.get("kwargs", {}),
270
271
  )
271
272
  if command.name == "save_config":
272
- return self.agent.config_manager.save()
273
+ namespace = command.payload.get("namespace", "agent")
274
+ return self.agent.config_manager.save_all() if namespace is None else self.agent.config_manager.save_config(namespace)
273
275
  if command.name == "save_session":
274
276
  return self.agent.save_session()
275
277
  if command.name == "compact_session":