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.
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/CHANGELOG.md +22 -0
- {codex_agent_framework-0.1.52/codex_agent_framework.egg-info → codex_agent_framework-0.1.54}/PKG-INFO +3 -2
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/README.md +2 -1
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/agent_runtime.py +16 -6
- codex_agent_framework-0.1.54/codex_agent/builtin_commands.py +196 -0
- codex_agent_framework-0.1.54/codex_agent/builtin_plugins/web_search/__init__.py +11 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/client.py +2 -2
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/config.py +155 -70
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/mainloop.py +4 -2
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/server/app.py +2 -1
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/server/core.py +5 -1
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/sessions.py +37 -4
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tool.py +1 -2
- codex_agent_framework-0.1.54/codex_agent/version.py +1 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54/codex_agent_framework.egg-info}/PKG-INFO +3 -2
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent_framework.egg-info/SOURCES.txt +1 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/pyproject.toml +1 -1
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent.py +16 -24
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_audio_hooks.py +7 -7
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_commands.py +56 -13
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_desktop.py +2 -2
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_environment.py +3 -3
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_image_generation.py +6 -4
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_observe.py +1 -1
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_plugins.py +16 -22
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_providers_config.py +34 -6
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_response.py +14 -11
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_runtime.py +24 -2
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_tools.py +9 -7
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_cli_headless.py +2 -1
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_hooks.py +1 -1
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_server.py +38 -3
- codex_agent_framework-0.1.52/codex_agent/builtin_commands.py +0 -152
- codex_agent_framework-0.1.52/codex_agent/version.py +0 -1
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/LICENSE +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/MANIFEST.in +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/__main__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/agent.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/ai.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/browser.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/bash/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/bash/requirements.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/browser/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/browser/requirements.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/content/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/content/requirements.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/content/system.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/content/vision.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/context/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/desktop/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/desktop/requirements.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/environment/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/environment/requirements.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/files/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/files/requirements.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/files/tools.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/image_generation/__init__.py +0 -0
- {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
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/image_generation/requirements.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/interface/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/interface/requirements.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/memory/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/memory/requirements.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/planner/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/planner/requirements.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/python/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/python/requirements.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/realtime/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/realtime/audio.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/realtime/bridge.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/realtime/manager.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/realtime/requirements.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/realtime/session.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/scheduler/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/scheduler/requirements.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/subagents/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/subagents/profiles/explorer.json +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/subagents/requirements.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/tts/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/builtin_plugins/tts/requirements.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/cli/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/cli/headless.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/cli/main.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/cli/output.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/cli/root.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/cli/runner.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/command.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/context.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/desktop.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/event.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/get_text/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/get_text/default_gitignore +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/get_text/get_text.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/get_text/simpler_get_text.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/hooks.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/image.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/latex.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/message.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/plugin.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/prompts/system_prompt.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/provider.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/runtime.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/scheduler.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/scripts/install-system-dependencies.sh +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/server/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/service.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/status.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/stream.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/stream_utils.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tray.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/__init__.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/app.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/chat.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/lifecycle.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/log.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/state.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/status_bar.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/style.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/tui/transcript.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/utils.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent/worker.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent_framework.egg-info/entry_points.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent_framework.egg-info/requires.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/codex_agent_framework.egg-info/top_level.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/dependencies.txt +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/setup.cfg +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_context.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_realtime.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_sessions.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_subagents.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_agent_turns.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_ai.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_browser.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_builtin_config.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_chat.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_cli.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_cli_root.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_client.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_events.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_get_text_browser.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_image_message.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_local_desktop.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_memory.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_messages.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_planner.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_scheduler.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_service.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_tui.py +0 -0
- {codex_agent_framework-0.1.52 → codex_agent_framework-0.1.54}/tests/test_utils.py +0 -0
- {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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
595
|
-
|
|
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.
|
|
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
|
-
|
|
16
|
-
|
|
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 =
|
|
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
|
-
|
|
71
|
-
|
|
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
|
|
113
|
-
return self.agent.submit("save_config").wait().result
|
|
114
|
-
config = self.
|
|
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
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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":
|