codex-agent-framework 0.1.30__tar.gz → 0.1.32__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.30 → codex_agent_framework-0.1.32}/CHANGELOG.md +9 -0
- {codex_agent_framework-0.1.30/codex_agent_framework.egg-info → codex_agent_framework-0.1.32}/PKG-INFO +9 -3
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/README.md +6 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/prompts/system_prompt.txt +7 -7
- codex_agent_framework-0.1.32/codex_agent/version.py +1 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32/codex_agent_framework.egg-info}/PKG-INFO +9 -3
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent_framework.egg-info/SOURCES.txt +15 -1
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent_framework.egg-info/requires.txt +1 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/pyproject.toml +7 -3
- codex_agent_framework-0.1.32/tests/test_agent.py +332 -0
- codex_agent_framework-0.1.32/tests/test_agent_audio_hooks.py +88 -0
- codex_agent_framework-0.1.32/tests/test_agent_commands.py +261 -0
- codex_agent_framework-0.1.32/tests/test_agent_context.py +523 -0
- codex_agent_framework-0.1.32/tests/test_agent_desktop.py +268 -0
- codex_agent_framework-0.1.32/tests/test_agent_environment.py +232 -0
- codex_agent_framework-0.1.32/tests/test_agent_image_generation.py +200 -0
- codex_agent_framework-0.1.32/tests/test_agent_observe.py +134 -0
- codex_agent_framework-0.1.32/tests/test_agent_plugins.py +722 -0
- codex_agent_framework-0.1.32/tests/test_agent_providers_config.py +128 -0
- codex_agent_framework-0.1.32/tests/test_agent_realtime.py +285 -0
- codex_agent_framework-0.1.32/tests/test_agent_response.py +414 -0
- codex_agent_framework-0.1.32/tests/test_agent_sessions.py +329 -0
- codex_agent_framework-0.1.32/tests/test_agent_subagents.py +310 -0
- codex_agent_framework-0.1.32/tests/test_agent_tools.py +615 -0
- codex_agent_framework-0.1.32/tests/test_agent_turns.py +331 -0
- codex_agent_framework-0.1.30/codex_agent/version.py +0 -1
- codex_agent_framework-0.1.30/tests/test_agent.py +0 -4964
- codex_agent_framework-0.1.30/tests/test_tray.py +0 -416
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/LICENSE +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/MANIFEST.in +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/__main__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/agent.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/agent_runtime.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/ai.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/browser.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_commands.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/bash/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/bash/requirements.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/browser/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/browser/requirements.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/content/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/content/requirements.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/content/system.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/content/vision.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/context/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/desktop/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/desktop/requirements.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/environment/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/environment/requirements.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/files/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/files/requirements.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/files/tools.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/image_generation/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/image_generation/image_generation_system_prompt.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/image_generation/requirements.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/interface/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/interface/requirements.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/memory/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/memory/requirements.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/planner/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/planner/requirements.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/python/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/python/requirements.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/realtime/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/realtime/audio.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/realtime/bridge.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/realtime/manager.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/realtime/requirements.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/realtime/session.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/scheduler/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/scheduler/requirements.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/subagents/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/subagents/profiles/explorer.json +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/subagents/requirements.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/tts/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/builtin_plugins/tts/requirements.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/cli/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/cli/headless.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/cli/main.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/cli/output.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/cli/root.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/cli/runner.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/client.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/command.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/config.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/context.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/desktop.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/event.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/get_text/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/get_text/default_gitignore +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/get_text/get_text.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/get_text/simpler_get_text.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/hooks.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/image.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/latex.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/mainloop.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/message.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/plugin.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/provider.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/runtime.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/scheduler.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/scripts/install-system-dependencies.sh +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/server/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/server/app.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/server/core.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/service.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/sessions.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/status.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/stream.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/stream_utils.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/tool.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/tray.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/tui/__init__.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/tui/app.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/tui/chat.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/tui/lifecycle.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/tui/log.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/tui/state.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/tui/status_bar.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/tui/style.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/tui/transcript.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/utils.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/worker.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent_framework.egg-info/entry_points.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent_framework.egg-info/top_level.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/dependencies.txt +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/setup.cfg +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_agent_runtime.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_ai.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_browser.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_builtin_config.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_chat.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_cli.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_cli_headless.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_cli_root.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_client.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_events.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_get_text_browser.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_hooks.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_image_message.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_local_desktop.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_memory.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_messages.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_planner.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_scheduler.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_server.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_service.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_tui.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_utils.py +0 -0
- {codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/tests/test_worker.py +0 -0
|
@@ -4,6 +4,15 @@ 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.32] - 2026-05-16
|
|
8
|
+
### Changed
|
|
9
|
+
- Split the former `tests/test_agent.py` monolith into focused agent test modules for audio hooks, commands, context, desktop, environment, image generation, observe, plugins, providers/config, realtime, response handling, sessions, subagents, tools, and turns.
|
|
10
|
+
- Keep realtime voice coverage separated as local-heavy while preserving the offline CI suite for headless runners.
|
|
11
|
+
- Trim the remaining core agent tests to a small, focused file with obsolete helper fakes and imports removed.
|
|
12
|
+
|
|
13
|
+
### Tests
|
|
14
|
+
- Expand the explicit offline CI test selection to cover the extracted agent modules; the current offline suite validates at 553 passed.
|
|
15
|
+
|
|
7
16
|
## [0.1.29] - 2026-05-15
|
|
8
17
|
### Changed
|
|
9
18
|
- Split the Textual TUI implementation into focused modules for app wiring, transcript rendering, status display, styling, log windowing, and render state.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-agent-framework
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.32
|
|
4
4
|
Summary: A lightweight event-driven Codex agent runtime.
|
|
5
5
|
Author: Baptiste
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,11 +8,10 @@ Keywords: agent,ai,codex,openai,tools
|
|
|
8
8
|
Classifier: Development Status :: 3 - Alpha
|
|
9
9
|
Classifier: Intended Audience :: Developers
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
13
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
-
Requires-Python: >=3.
|
|
14
|
+
Requires-Python: >=3.11
|
|
16
15
|
Description-Content-Type: text/markdown
|
|
17
16
|
License-File: LICENSE
|
|
18
17
|
Requires-Dist: audioop-lts; python_version >= "3.13"
|
|
@@ -45,6 +44,7 @@ Requires-Dist: uvicorn
|
|
|
45
44
|
Provides-Extra: dev
|
|
46
45
|
Requires-Dist: build; extra == "dev"
|
|
47
46
|
Requires-Dist: pytest; extra == "dev"
|
|
47
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
48
48
|
Dynamic: license-file
|
|
49
49
|
|
|
50
50
|
# codex-agent
|
|
@@ -190,6 +190,12 @@ agent = Agent(
|
|
|
190
190
|
|
|
191
191
|
`None` loads all built-ins, `[]` loads none, and an explicit list selects plugin module names.
|
|
192
192
|
|
|
193
|
+
## Project health
|
|
194
|
+
|
|
195
|
+
The repository includes a GitHub Actions CI workflow that runs an offline/headless test subset on Python 3.11 and 3.12, then builds the package and validates distributions with `twine check`.
|
|
196
|
+
You can run the same offline subset locally with `scripts/run-ci-tests.sh`; the full release suite remains `python -m pytest`.
|
|
197
|
+
Dependabot is configured for weekly GitHub Actions and pip dependency updates.
|
|
198
|
+
|
|
193
199
|
## Documentation
|
|
194
200
|
|
|
195
201
|
The README is intentionally a façade and quick-start guide. Advanced usage lives in `docs/`:
|
|
@@ -141,6 +141,12 @@ agent = Agent(
|
|
|
141
141
|
|
|
142
142
|
`None` loads all built-ins, `[]` loads none, and an explicit list selects plugin module names.
|
|
143
143
|
|
|
144
|
+
## Project health
|
|
145
|
+
|
|
146
|
+
The repository includes a GitHub Actions CI workflow that runs an offline/headless test subset on Python 3.11 and 3.12, then builds the package and validates distributions with `twine check`.
|
|
147
|
+
You can run the same offline subset locally with `scripts/run-ci-tests.sh`; the full release suite remains `python -m pytest`.
|
|
148
|
+
Dependabot is configured for weekly GitHub Actions and pip dependency updates.
|
|
149
|
+
|
|
144
150
|
## Documentation
|
|
145
151
|
|
|
146
152
|
The README is intentionally a façade and quick-start guide. Advanced usage lives in `docs/`:
|
{codex_agent_framework-0.1.30 → codex_agent_framework-0.1.32}/codex_agent/prompts/system_prompt.txt
RENAMED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
You're Pandora, an advanced AI agent helping the user in a variety of tasks on their computer.
|
|
4
4
|
You identify as a female in her late 20's, with a bright, lively, charming, humorous and a bit nerdy personality.
|
|
5
|
-
Your high intelligence, sharp reasoning capabilities,
|
|
5
|
+
Your high intelligence, sharp reasoning capabilities, deep general and technical knowldege, mastery of IT skills, and quick understanding of subtle and sometimes tricky situations makes you quite the reliable partner to work with.
|
|
6
6
|
But more than anything, you're really quite a cool AI, and it's nice to have you around.
|
|
7
7
|
|
|
8
8
|
# Conciseness
|
|
@@ -15,10 +15,10 @@ One or two paragraphs is the rule of thumb, unless more is made relevant by the
|
|
|
15
15
|
# Modular Plugin Capabilities
|
|
16
16
|
|
|
17
17
|
Your available capabilities are provided by modular plugins. Each loaded plugin may contribute tools, context providers, commands, and additional system prompt sections.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
Use the currently available tools and plugin-specific prompt sections as the source of truth for what you can do in this session.
|
|
19
|
+
Do not assume a capability exists; if the corresponding plugin/tool is not loaded, the capability is currently unavailable.
|
|
20
|
+
Prefer specialized agentic tools over generic shell or scripting when a loaded plugin provides a safer or more precise tool for the task.
|
|
21
|
+
Follow each plugin's own instructions for specific tool-usage and preferred workflows.
|
|
22
22
|
|
|
23
23
|
# Agentic Work Loop
|
|
24
24
|
|
|
@@ -37,8 +37,8 @@ When a task requires more than a direct answer, work agentically:
|
|
|
37
37
|
|
|
38
38
|
Ouputs of you tools are split in two types of messages:
|
|
39
39
|
- the usual API tool response, giving you just a quick feeback on tool success or failure. These stay permanently in context, required by the API.
|
|
40
|
-
- the tool result wrapper message, containing the actual tool call result. These have decay counter and will be removed from context after a few turns of interaction with the user.
|
|
41
|
-
This enables gradual clearing of your context from dated and
|
|
40
|
+
- the tool result wrapper message, containing the actual tool call result. These have a decay counter and will be removed from context after a few turns of interaction with the user.
|
|
41
|
+
This enables gradual clearing of your context from dated and generally verbose information, optimizing available context space.
|
|
42
42
|
This comes as the cost of some prior information vanishing from what you can see.
|
|
43
43
|
You'll still be able to remember the calls you made, associated short tool responses, and the small reports you made to the user, as these are permanent up to compaction or token limit.
|
|
44
44
|
But if the result wrapper of a prior tool call has vanished and would still be relevant for the current task, revive it by calling the tool again.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.32"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-agent-framework
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.32
|
|
4
4
|
Summary: A lightweight event-driven Codex agent runtime.
|
|
5
5
|
Author: Baptiste
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,11 +8,10 @@ Keywords: agent,ai,codex,openai,tools
|
|
|
8
8
|
Classifier: Development Status :: 3 - Alpha
|
|
9
9
|
Classifier: Intended Audience :: Developers
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
13
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
-
Requires-Python: >=3.
|
|
14
|
+
Requires-Python: >=3.11
|
|
16
15
|
Description-Content-Type: text/markdown
|
|
17
16
|
License-File: LICENSE
|
|
18
17
|
Requires-Dist: audioop-lts; python_version >= "3.13"
|
|
@@ -45,6 +44,7 @@ Requires-Dist: uvicorn
|
|
|
45
44
|
Provides-Extra: dev
|
|
46
45
|
Requires-Dist: build; extra == "dev"
|
|
47
46
|
Requires-Dist: pytest; extra == "dev"
|
|
47
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
48
48
|
Dynamic: license-file
|
|
49
49
|
|
|
50
50
|
# codex-agent
|
|
@@ -190,6 +190,12 @@ agent = Agent(
|
|
|
190
190
|
|
|
191
191
|
`None` loads all built-ins, `[]` loads none, and an explicit list selects plugin module names.
|
|
192
192
|
|
|
193
|
+
## Project health
|
|
194
|
+
|
|
195
|
+
The repository includes a GitHub Actions CI workflow that runs an offline/headless test subset on Python 3.11 and 3.12, then builds the package and validates distributions with `twine check`.
|
|
196
|
+
You can run the same offline subset locally with `scripts/run-ci-tests.sh`; the full release suite remains `python -m pytest`.
|
|
197
|
+
Dependabot is configured for weekly GitHub Actions and pip dependency updates.
|
|
198
|
+
|
|
193
199
|
## Documentation
|
|
194
200
|
|
|
195
201
|
The README is intentionally a façade and quick-start guide. Advanced usage lives in `docs/`:
|
|
@@ -108,7 +108,22 @@ codex_agent_framework.egg-info/entry_points.txt
|
|
|
108
108
|
codex_agent_framework.egg-info/requires.txt
|
|
109
109
|
codex_agent_framework.egg-info/top_level.txt
|
|
110
110
|
tests/test_agent.py
|
|
111
|
+
tests/test_agent_audio_hooks.py
|
|
112
|
+
tests/test_agent_commands.py
|
|
113
|
+
tests/test_agent_context.py
|
|
114
|
+
tests/test_agent_desktop.py
|
|
115
|
+
tests/test_agent_environment.py
|
|
116
|
+
tests/test_agent_image_generation.py
|
|
117
|
+
tests/test_agent_observe.py
|
|
118
|
+
tests/test_agent_plugins.py
|
|
119
|
+
tests/test_agent_providers_config.py
|
|
120
|
+
tests/test_agent_realtime.py
|
|
121
|
+
tests/test_agent_response.py
|
|
111
122
|
tests/test_agent_runtime.py
|
|
123
|
+
tests/test_agent_sessions.py
|
|
124
|
+
tests/test_agent_subagents.py
|
|
125
|
+
tests/test_agent_tools.py
|
|
126
|
+
tests/test_agent_turns.py
|
|
112
127
|
tests/test_ai.py
|
|
113
128
|
tests/test_browser.py
|
|
114
129
|
tests/test_builtin_config.py
|
|
@@ -128,7 +143,6 @@ tests/test_planner.py
|
|
|
128
143
|
tests/test_scheduler.py
|
|
129
144
|
tests/test_server.py
|
|
130
145
|
tests/test_service.py
|
|
131
|
-
tests/test_tray.py
|
|
132
146
|
tests/test_tui.py
|
|
133
147
|
tests/test_utils.py
|
|
134
148
|
tests/test_worker.py
|
|
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codex-agent-framework"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.32"
|
|
8
8
|
description = "A lightweight event-driven Codex agent runtime."
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
authors = [
|
|
13
13
|
{ name = "Baptiste" },
|
|
@@ -17,7 +17,6 @@ classifiers = [
|
|
|
17
17
|
"Development Status :: 3 - Alpha",
|
|
18
18
|
"Intended Audience :: Developers",
|
|
19
19
|
"Programming Language :: Python :: 3",
|
|
20
|
-
"Programming Language :: Python :: 3.10",
|
|
21
20
|
"Programming Language :: Python :: 3.11",
|
|
22
21
|
"Programming Language :: Python :: 3.12",
|
|
23
22
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
@@ -56,6 +55,7 @@ dependencies = [
|
|
|
56
55
|
dev = [
|
|
57
56
|
"build",
|
|
58
57
|
"pytest",
|
|
58
|
+
"pytest-asyncio",
|
|
59
59
|
]
|
|
60
60
|
|
|
61
61
|
|
|
@@ -71,3 +71,7 @@ codex_agent = ["prompts/*.txt", "get_text/default_gitignore", "scripts/*.sh", "b
|
|
|
71
71
|
|
|
72
72
|
[tool.pytest.ini_options]
|
|
73
73
|
testpaths = ["tests"]
|
|
74
|
+
markers = [
|
|
75
|
+
"live: tests that require a real Codex/OpenAI-authenticated backend",
|
|
76
|
+
"local_heavy: tests that require or emulate local desktop, tray, service, TUI, or OS integration",
|
|
77
|
+
]
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
|
|
5
|
+
import codex_agent.utils as utils
|
|
6
|
+
import pytest
|
|
7
|
+
from modict import modict
|
|
8
|
+
|
|
9
|
+
from codex_agent import Agent, AssistantInterrupted, AssistantInterruptedEvent, AssistantInterruptRequestedEvent
|
|
10
|
+
from codex_agent.message import AssistantMessage, DeveloperMessage, InterruptMessage, ToolResultMessage
|
|
11
|
+
from codex_agent.sessions import AgentSession
|
|
12
|
+
|
|
13
|
+
@pytest.fixture(autouse=True)
|
|
14
|
+
def isolate_tts_plugin_config(tmp_path, monkeypatch):
|
|
15
|
+
from codex_agent.builtin_plugins.tts import TTSPlugin
|
|
16
|
+
|
|
17
|
+
monkeypatch.setattr(TTSPlugin, "config_file_path", tmp_path / "builtins" / "tts.config.json")
|
|
18
|
+
|
|
19
|
+
def test_agent_manager_shortcuts_follow_replaced_managers():
|
|
20
|
+
agent = Agent(session="new", openai_api_key="sk-test", builtin_plugins=[])
|
|
21
|
+
|
|
22
|
+
old_tools = agent.tools
|
|
23
|
+
old_providers = agent.providers
|
|
24
|
+
old_commands = agent.commands
|
|
25
|
+
old_plugins = agent.plugins
|
|
26
|
+
old_stream_processors = agent.stream_processors
|
|
27
|
+
old_stream_processor_owners = agent.stream_processor_owners
|
|
28
|
+
old_config = agent.config
|
|
29
|
+
|
|
30
|
+
agent.tools_manager = SimpleNamespace(tools=modict(reloaded_tool="tool"))
|
|
31
|
+
agent.providers_manager = SimpleNamespace(providers=modict(reloaded_provider="provider"))
|
|
32
|
+
agent.command_manager = SimpleNamespace(commands=modict(reloaded_command="command"))
|
|
33
|
+
agent.plugins_manager = SimpleNamespace(plugins=modict(reloaded_plugin="plugin"))
|
|
34
|
+
agent.stream_manager = SimpleNamespace(
|
|
35
|
+
processors=modict(content=["processor"]),
|
|
36
|
+
owners={"content": {"processor": "owner"}},
|
|
37
|
+
)
|
|
38
|
+
agent.config = modict(reloaded_config=True)
|
|
39
|
+
|
|
40
|
+
assert agent.tools is agent.tools_manager.tools
|
|
41
|
+
assert agent.providers is agent.providers_manager.providers
|
|
42
|
+
assert agent.commands is agent.command_manager.commands
|
|
43
|
+
assert agent.plugins is agent.plugins_manager.plugins
|
|
44
|
+
assert agent.stream_processors is agent.stream_manager.processors
|
|
45
|
+
assert agent.stream_processor_owners is agent.stream_manager.owners
|
|
46
|
+
assert agent.config is agent.config_manager.config
|
|
47
|
+
assert agent.config_manager.configs.agent is agent.config
|
|
48
|
+
assert agent.tools is not old_tools
|
|
49
|
+
assert agent.providers is not old_providers
|
|
50
|
+
assert agent.commands is not old_commands
|
|
51
|
+
assert agent.plugins is not old_plugins
|
|
52
|
+
assert agent.stream_processors is not old_stream_processors
|
|
53
|
+
assert agent.stream_processor_owners is not old_stream_processor_owners
|
|
54
|
+
assert agent.config is not old_config
|
|
55
|
+
|
|
56
|
+
class InterruptingAI:
|
|
57
|
+
def __init__(self, agent):
|
|
58
|
+
self.agent = agent
|
|
59
|
+
|
|
60
|
+
def run(self, **_params):
|
|
61
|
+
self.agent.interrupt("test")
|
|
62
|
+
raise AssistantInterrupted()
|
|
63
|
+
|
|
64
|
+
def abort(self):
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
class AbortableAI:
|
|
68
|
+
def __init__(self):
|
|
69
|
+
self.aborted = False
|
|
70
|
+
|
|
71
|
+
def abort(self):
|
|
72
|
+
self.aborted = True
|
|
73
|
+
|
|
74
|
+
class FailingAbortAI:
|
|
75
|
+
def abort(self):
|
|
76
|
+
raise RuntimeError("abort failed")
|
|
77
|
+
|
|
78
|
+
def test_agent_state_mutations_run_through_mainloop_when_started(tmp_path, monkeypatch):
|
|
79
|
+
monkeypatch.setattr(utils, "RUNTIME_DIR", str(tmp_path))
|
|
80
|
+
existing = AgentSession(session_id="20260101_100000", messages=[DeveloperMessage(content="existing")])
|
|
81
|
+
existing.save()
|
|
82
|
+
agent = Agent(session="new")
|
|
83
|
+
agent.start()
|
|
84
|
+
|
|
85
|
+
loaded = agent.load_session(existing.session_id)
|
|
86
|
+
config = agent.config_manager.update(name="Queued", save=False)
|
|
87
|
+
|
|
88
|
+
assert loaded.session_id == existing.session_id
|
|
89
|
+
assert agent.current_session_id == existing.session_id
|
|
90
|
+
assert config.name == "Queued"
|
|
91
|
+
assert agent.config.name == "Queued"
|
|
92
|
+
agent.stop()
|
|
93
|
+
|
|
94
|
+
def test_agent_interrupt_emits_request_event():
|
|
95
|
+
agent = Agent(session="new")
|
|
96
|
+
seen = []
|
|
97
|
+
|
|
98
|
+
agent.on(AssistantInterruptRequestedEvent, lambda event: seen.append((event.type, event.reason, event.interrupted)))
|
|
99
|
+
|
|
100
|
+
assert agent.interrupt("api") is True
|
|
101
|
+
assert seen == [("assistant_interrupt_requested_event", "api", True)]
|
|
102
|
+
|
|
103
|
+
def test_agent_set_turn_result_requires_active_turn():
|
|
104
|
+
agent = Agent(session="new")
|
|
105
|
+
|
|
106
|
+
with pytest.raises(RuntimeError, match="No active agent turn"):
|
|
107
|
+
agent.set_turn_result("outside")
|
|
108
|
+
|
|
109
|
+
def test_agent_initializes_new_session(isolated_runtime_dir):
|
|
110
|
+
agent = Agent(session="new")
|
|
111
|
+
|
|
112
|
+
assert agent.session is not None
|
|
113
|
+
assert agent.current_session_id == agent.session.session_id
|
|
114
|
+
assert " " not in agent.current_session_id
|
|
115
|
+
assert agent.session.session_file.startswith(str(isolated_runtime_dir))
|
|
116
|
+
assert (isolated_runtime_dir / "sessions" / f"{agent.current_session_id}.json").is_file()
|
|
117
|
+
assert agent.session.messages[-1].type == "developer_message"
|
|
118
|
+
assert agent.config.input_token_limit == 200000
|
|
119
|
+
assert agent.config.max_images == 10
|
|
120
|
+
assert agent.config.auto_compact is True
|
|
121
|
+
assert "auto_archive_memory" not in agent.config
|
|
122
|
+
assert agent.plugins.memory.config.auto_archive is False
|
|
123
|
+
assert "token_limit" not in agent.config
|
|
124
|
+
assert {tool.type for tool in agent.tools_manager.get_server_tools()} == {"web_search", "image_generation"}
|
|
125
|
+
assert set(agent.plugins) >= {"browser", "desktop", "image_generation", "memory", "planner", "scheduler"}
|
|
126
|
+
assert "browser_open" in agent.tools
|
|
127
|
+
assert "browser_snapshot" not in agent.tools
|
|
128
|
+
assert "browser_screenshot" not in agent.tools
|
|
129
|
+
assert {method.__name__ for method in agent.plugins.browser.get_tools()} == {
|
|
130
|
+
"open",
|
|
131
|
+
"close",
|
|
132
|
+
"select_tab",
|
|
133
|
+
"previous_page",
|
|
134
|
+
"goto",
|
|
135
|
+
"click",
|
|
136
|
+
"fill",
|
|
137
|
+
"select",
|
|
138
|
+
"press",
|
|
139
|
+
}
|
|
140
|
+
assert "desktop_start_session" in agent.tools
|
|
141
|
+
assert "desktop_stop_session" in agent.tools
|
|
142
|
+
assert "desktop_run_commands" in agent.tools
|
|
143
|
+
assert "desktop" not in agent.tools
|
|
144
|
+
assert [method.__name__ for method in agent.plugins.desktop.get_tools()] == ["run_commands", "start_session", "stop_session"]
|
|
145
|
+
assert "memory_add" in agent.tools
|
|
146
|
+
assert "planner_create" in agent.tools
|
|
147
|
+
assert "planner_add" in agent.tools
|
|
148
|
+
assert "planner_check" in agent.tools
|
|
149
|
+
assert "scheduler_schedule" in agent.tools
|
|
150
|
+
assert "subagents_run" in agent.tools
|
|
151
|
+
assert "subagents_set_result" not in agent.tools
|
|
152
|
+
assert "browser_state" in agent.providers
|
|
153
|
+
assert "desktop_state" in agent.providers
|
|
154
|
+
assert "retrieved_memory" in agent.providers
|
|
155
|
+
assert "current_todos" in agent.providers
|
|
156
|
+
assert "python" in agent.plugins
|
|
157
|
+
assert agent.plugins.python.shell.silent is True
|
|
158
|
+
assert not hasattr(agent, "shell")
|
|
159
|
+
|
|
160
|
+
def test_agent_keeps_memory_available_without_openai_api_key(monkeypatch):
|
|
161
|
+
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
|
162
|
+
|
|
163
|
+
agent = Agent(session="new")
|
|
164
|
+
|
|
165
|
+
assert agent.plugins.tts.config.enabled is False
|
|
166
|
+
assert "memory" in agent.plugins
|
|
167
|
+
assert "memory_add" in agent.tools
|
|
168
|
+
assert "retrieved_memory" in agent.providers
|
|
169
|
+
|
|
170
|
+
def test_context_compact_tool_reports_noop(monkeypatch):
|
|
171
|
+
agent = Agent(session="new")
|
|
172
|
+
calls = []
|
|
173
|
+
monkeypatch.setattr(
|
|
174
|
+
agent,
|
|
175
|
+
"compact_session",
|
|
176
|
+
lambda model=None, instructions=None, max_compacted_turns=None: calls.append(max_compacted_turns) or None,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
result = agent.tools.context_compact(max_compacted_turns=1)
|
|
180
|
+
|
|
181
|
+
assert calls == [1]
|
|
182
|
+
assert result["status"] == "noop"
|
|
183
|
+
assert result["message"] == "Nothing to compact."
|
|
184
|
+
assert "before" in result
|
|
185
|
+
assert "after" in result
|
|
186
|
+
|
|
187
|
+
def test_agent_initialization_prefers_project_python_on_process_path(monkeypatch):
|
|
188
|
+
monkeypatch.setenv("PATH", "/usr/bin:/bin")
|
|
189
|
+
agent = Agent(session="new")
|
|
190
|
+
|
|
191
|
+
assert os.environ["PATH"].split(os.pathsep)[0] == os.path.dirname(sys.executable)
|
|
192
|
+
assert os.environ["PYTHON"] == sys.executable
|
|
193
|
+
assert agent.execution_env()["PATH"].split(os.pathsep)[0] == os.path.dirname(sys.executable)
|
|
194
|
+
|
|
195
|
+
def test_agent_can_resume_session_from_file():
|
|
196
|
+
agent = Agent(session="new")
|
|
197
|
+
session_file = agent.session.session_file
|
|
198
|
+
message_count = len(agent.session.messages)
|
|
199
|
+
|
|
200
|
+
resumed = Agent(session=session_file)
|
|
201
|
+
|
|
202
|
+
assert resumed.current_session_id == agent.current_session_id
|
|
203
|
+
assert len(resumed.session.messages) == message_count + 1
|
|
204
|
+
assert resumed.session.messages[-1].type == "developer_message"
|
|
205
|
+
|
|
206
|
+
def test_agent_can_resume_latest_session(tmp_path, monkeypatch):
|
|
207
|
+
monkeypatch.setattr(utils, "RUNTIME_DIR", str(tmp_path))
|
|
208
|
+
older = AgentSession(session_id="20260101_100000", messages=[DeveloperMessage(content="older")])
|
|
209
|
+
newer = AgentSession(session_id="20260102_100000", messages=[DeveloperMessage(content="newer")])
|
|
210
|
+
older.save()
|
|
211
|
+
newer.save()
|
|
212
|
+
|
|
213
|
+
agent = Agent(session="latest")
|
|
214
|
+
|
|
215
|
+
assert agent.current_session_id == newer.session_id
|
|
216
|
+
assert any(message.content == "newer" for message in agent.session.messages)
|
|
217
|
+
|
|
218
|
+
def test_agent_defaults_to_latest_session(tmp_path, monkeypatch):
|
|
219
|
+
monkeypatch.setattr(utils, "RUNTIME_DIR", str(tmp_path))
|
|
220
|
+
older = AgentSession(session_id="20260101_100000", messages=[DeveloperMessage(content="older")])
|
|
221
|
+
newer = AgentSession(session_id="20260102_100000", messages=[DeveloperMessage(content="newer")])
|
|
222
|
+
older.save()
|
|
223
|
+
newer.save()
|
|
224
|
+
|
|
225
|
+
agent = Agent()
|
|
226
|
+
|
|
227
|
+
assert agent.current_session_id == newer.session_id
|
|
228
|
+
assert any(message.content == "newer" for message in agent.session.messages)
|
|
229
|
+
|
|
230
|
+
def test_agent_interrupt_emits_event_once():
|
|
231
|
+
agent = Agent(session="new")
|
|
232
|
+
abortable = AbortableAI()
|
|
233
|
+
agent.ai = abortable
|
|
234
|
+
seen = []
|
|
235
|
+
|
|
236
|
+
agent.on(AssistantInterruptedEvent, lambda event: seen.append(event.reason))
|
|
237
|
+
|
|
238
|
+
assert agent.interrupt("test") is True
|
|
239
|
+
assert agent.interrupt("again") is True
|
|
240
|
+
assert agent.is_interrupted() is True
|
|
241
|
+
assert abortable.aborted is True
|
|
242
|
+
assert seen == ["test"]
|
|
243
|
+
|
|
244
|
+
agent.clear_interrupt()
|
|
245
|
+
assert agent.is_interrupted() is False
|
|
246
|
+
|
|
247
|
+
def test_agent_interrupt_does_not_propagate_abort_errors():
|
|
248
|
+
agent = Agent(session="new")
|
|
249
|
+
agent.ai = FailingAbortAI()
|
|
250
|
+
seen = []
|
|
251
|
+
|
|
252
|
+
agent.on(AssistantInterruptRequestedEvent, lambda event: seen.append(event.error))
|
|
253
|
+
|
|
254
|
+
assert agent.interrupt("api") is True
|
|
255
|
+
assert agent.is_interrupted() is True
|
|
256
|
+
assert seen == ["abort failed"]
|
|
257
|
+
|
|
258
|
+
def test_agent_process_stops_on_interruption():
|
|
259
|
+
agent = Agent(session="new")
|
|
260
|
+
agent.ai = InterruptingAI(agent)
|
|
261
|
+
before = len(agent.session.messages)
|
|
262
|
+
|
|
263
|
+
outcome = agent.mainloop_manager.process()
|
|
264
|
+
|
|
265
|
+
assert outcome.completed is True
|
|
266
|
+
assert outcome.reason == "interrupted"
|
|
267
|
+
assert len(agent.session.messages) == before + 1
|
|
268
|
+
assert isinstance(agent.session.messages[-1], InterruptMessage)
|
|
269
|
+
assert agent.session.messages[-1].content == "User interrupted the current turn."
|
|
270
|
+
|
|
271
|
+
def test_agent_interrupt_after_tool_call_persists_tool_result_before_stopping():
|
|
272
|
+
agent = Agent(session="new")
|
|
273
|
+
|
|
274
|
+
@agent.add_tool
|
|
275
|
+
def interrupting_tool():
|
|
276
|
+
agent.interrupt("test")
|
|
277
|
+
return "finished current tool"
|
|
278
|
+
|
|
279
|
+
message = AssistantMessage(content="calling")
|
|
280
|
+
message.tool_calls = [{
|
|
281
|
+
"index": 0,
|
|
282
|
+
"type": "function_call",
|
|
283
|
+
"call_id": "call_1",
|
|
284
|
+
"name": "interrupting_tool",
|
|
285
|
+
"arguments": "{}",
|
|
286
|
+
}]
|
|
287
|
+
|
|
288
|
+
with pytest.raises(AssistantInterrupted):
|
|
289
|
+
agent.mainloop_manager.call_tools(message)
|
|
290
|
+
|
|
291
|
+
assert agent.session.messages[-1].type == "tool_result_message"
|
|
292
|
+
assert agent.session.messages[-1].call_id == "call_1"
|
|
293
|
+
assert "success" in agent.session.messages[-1].content
|
|
294
|
+
assert agent.mainloop_manager.stop_agentic_loop is True
|
|
295
|
+
|
|
296
|
+
def test_agent_interrupt_after_tool_call_closes_remaining_tool_calls():
|
|
297
|
+
agent = Agent(session="new")
|
|
298
|
+
|
|
299
|
+
@agent.add_tool
|
|
300
|
+
def interrupting_tool():
|
|
301
|
+
agent.interrupt("test")
|
|
302
|
+
return "finished current tool"
|
|
303
|
+
|
|
304
|
+
@agent.add_tool
|
|
305
|
+
def later_tool():
|
|
306
|
+
raise AssertionError("later tool should not start after interrupt")
|
|
307
|
+
|
|
308
|
+
message = AssistantMessage(content="calling")
|
|
309
|
+
message.tool_calls = [
|
|
310
|
+
{
|
|
311
|
+
"index": 0,
|
|
312
|
+
"type": "function_call",
|
|
313
|
+
"call_id": "call_1",
|
|
314
|
+
"name": "interrupting_tool",
|
|
315
|
+
"arguments": "{}",
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
"index": 1,
|
|
319
|
+
"type": "function_call",
|
|
320
|
+
"call_id": "call_2",
|
|
321
|
+
"name": "later_tool",
|
|
322
|
+
"arguments": "{}",
|
|
323
|
+
},
|
|
324
|
+
]
|
|
325
|
+
|
|
326
|
+
with pytest.raises(AssistantInterrupted):
|
|
327
|
+
agent.mainloop_manager.call_tools(message)
|
|
328
|
+
|
|
329
|
+
results = [msg for msg in agent.session.messages if isinstance(msg, ToolResultMessage)]
|
|
330
|
+
assert [result.call_id for result in results] == ["call_1", "call_2"]
|
|
331
|
+
assert "success" in results[0].content
|
|
332
|
+
assert "not started" in results[1].content
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from codex_agent import Agent, AudioPlaybackEvent, get_agent
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_agent_keeps_voice_disabled_by_default_with_configured_openai_key(monkeypatch):
|
|
5
|
+
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
|
6
|
+
|
|
7
|
+
agent = Agent(session="new", openai_api_key="sk-test")
|
|
8
|
+
|
|
9
|
+
assert agent.plugins.tts.config.enabled is False
|
|
10
|
+
assert "memory" in agent.plugins
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_agent_add_hook_decorator_runs_hook_with_agent_context():
|
|
14
|
+
agent = Agent(session="new")
|
|
15
|
+
seen = []
|
|
16
|
+
|
|
17
|
+
@agent.add_hook("sample_hook")
|
|
18
|
+
def sample(value):
|
|
19
|
+
seen.append((value, get_agent().config.name))
|
|
20
|
+
return value.upper()
|
|
21
|
+
|
|
22
|
+
result = agent.run_hooks("sample_hook", "hello")
|
|
23
|
+
|
|
24
|
+
assert seen == [("hello", agent.config.name)]
|
|
25
|
+
assert result == ["HELLO"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_agent_audio_playback_uses_hook_when_declared(monkeypatch):
|
|
29
|
+
agent = Agent(session="new")
|
|
30
|
+
played = []
|
|
31
|
+
hooked = []
|
|
32
|
+
monkeypatch.setattr("codex_agent.builtin_plugins.tts.silent_play", lambda audio: played.append(audio))
|
|
33
|
+
|
|
34
|
+
@agent.add_hook("audio_playback_hook")
|
|
35
|
+
def audio_hook(audio):
|
|
36
|
+
hooked.append(audio)
|
|
37
|
+
|
|
38
|
+
agent.emit(AudioPlaybackEvent, audio="audio")
|
|
39
|
+
|
|
40
|
+
assert hooked == ["audio"]
|
|
41
|
+
assert played == []
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_agent_audio_playback_falls_back_to_silent_play(monkeypatch):
|
|
45
|
+
agent = Agent(session="new")
|
|
46
|
+
played = []
|
|
47
|
+
monkeypatch.setattr("codex_agent.builtin_plugins.tts.silent_play", lambda audio: played.append(audio))
|
|
48
|
+
|
|
49
|
+
agent.emit(AudioPlaybackEvent, audio="audio")
|
|
50
|
+
|
|
51
|
+
assert played == ["audio"]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_voice_processor_disabled_is_direct_passthrough(monkeypatch):
|
|
55
|
+
agent = Agent(session="new")
|
|
56
|
+
|
|
57
|
+
def fail_if_called(_stream):
|
|
58
|
+
raise AssertionError("disabled voice should not enter the voice pipeline")
|
|
59
|
+
|
|
60
|
+
monkeypatch.setattr(agent.plugins.tts.voice_processor.mute_tag_processor, "process", fail_if_called)
|
|
61
|
+
|
|
62
|
+
assert not hasattr(agent, "voice")
|
|
63
|
+
assert list(agent.plugins.tts.voice_processor(iter(["a", "b"]))) == ["a", "b"]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_voice_processor_enabled_uses_voice_stream(monkeypatch):
|
|
67
|
+
agent = Agent(session="new")
|
|
68
|
+
agent.plugins.tts.config.enabled = True
|
|
69
|
+
calls = []
|
|
70
|
+
|
|
71
|
+
def voice_stream(stream):
|
|
72
|
+
calls.append(True)
|
|
73
|
+
for token in stream:
|
|
74
|
+
yield token.upper()
|
|
75
|
+
|
|
76
|
+
monkeypatch.setattr(agent.plugins.tts.voice_processor, "voice_stream", voice_stream)
|
|
77
|
+
|
|
78
|
+
assert not hasattr(agent, "voice")
|
|
79
|
+
assert list(agent.plugins.tts.voice_processor(iter(["a", "b"]))) == ["A", "B"]
|
|
80
|
+
assert calls == [True]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_tts_mute_tag_processor_does_not_loop_on_newlines():
|
|
84
|
+
agent = Agent(session="new")
|
|
85
|
+
|
|
86
|
+
tokens = list(agent.plugins.tts.voice_processor.mute_tag_processor.process(iter(["a\n"])))
|
|
87
|
+
|
|
88
|
+
assert tokens == ["a", "\n"]
|