codex-agent-framework 0.1.23__tar.gz → 0.1.24__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.23 → codex_agent_framework-0.1.24}/CHANGELOG.md +11 -0
- {codex_agent_framework-0.1.23/codex_agent_framework.egg-info → codex_agent_framework-0.1.24}/PKG-INFO +3 -2
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/README.md +2 -1
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/__init__.py +4 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/command.py +17 -4
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/event.py +15 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/tui/chat.py +61 -0
- codex_agent_framework-0.1.24/codex_agent/version.py +1 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24/codex_agent_framework.egg-info}/PKG-INFO +3 -2
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/pyproject.toml +1 -1
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_agent.py +43 -1
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_chat.py +41 -0
- codex_agent_framework-0.1.23/codex_agent/version.py +0 -1
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/LICENSE +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/MANIFEST.in +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/__main__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/agent.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/agent_runtime.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/ai.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/browser.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_commands.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/bash/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/browser/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/content/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/content/system.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/content/vision.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/desktop/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/environment/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/files/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/files/tools.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/interface/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/memory/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/planner/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/python/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/scheduler/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/subagents/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/subagents/profiles/explorer.json +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/cli/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/cli/headless.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/cli/main.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/cli/output.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/cli/root.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/cli/runner.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/client.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/config.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/context.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/desktop.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/get_text/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/get_text/default_gitignore +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/get_text/get_text.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/get_text/simpler_get_text.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/hooks.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/image.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/latex.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/mainloop.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/message.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/plugin.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/prompts/image_generation_system_prompt.txt +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/prompts/system_prompt.txt +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/provider.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/runtime.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/scheduler.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/scripts/install-system-dependencies.sh +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/server/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/server/app.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/server/core.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/service.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/sessions.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/status.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/stream.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/stream_utils.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/tool.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/tray.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/tui/__init__.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/tui/lifecycle.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/utils.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/voice.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/worker.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent_framework.egg-info/SOURCES.txt +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent_framework.egg-info/entry_points.txt +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent_framework.egg-info/requires.txt +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent_framework.egg-info/top_level.txt +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/dependencies.txt +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/setup.cfg +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_agent_runtime.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_ai.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_browser.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_builtin_config.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_cli.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_cli_headless.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_cli_root.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_client.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_events.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_get_text_browser.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_hooks.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_image_message.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_local_desktop.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_memory.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_messages.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_planner.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_scheduler.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_server.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_service.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_tray.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_tui.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_utils.py +0 -0
- {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_worker.py +0 -0
|
@@ -4,6 +4,17 @@ 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.24] - 2026-05-10
|
|
8
|
+
### Added
|
|
9
|
+
- Add dedicated slash-command lifecycle events, `SlashCommandStartEvent` and `SlashCommandDoneEvent`, with command name, arguments, status, result, and error metadata.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Show slash-command progress directly in the Textual TUI with running/done/failed status lines so commands such as `/compact` and `/help` no longer appear silent while executing.
|
|
13
|
+
- Render slash-command results and errors immediately in the TUI after completion.
|
|
14
|
+
|
|
15
|
+
### Tests
|
|
16
|
+
- Add regression coverage for slash-command lifecycle events, failed command reporting, and TUI rendering of command progress, results, and errors.
|
|
17
|
+
|
|
7
18
|
## [0.1.23] - 2026-05-10
|
|
8
19
|
### Added
|
|
9
20
|
- Add the SDK `Agent.stream(prompt=None)` API for iterating turn events while still exposing the final `AgentTurn` through `.turn`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-agent-framework
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.24
|
|
4
4
|
Summary: A lightweight event-driven Codex agent runtime.
|
|
5
5
|
Author: Baptiste
|
|
6
6
|
License-Expression: MIT
|
|
@@ -610,7 +610,7 @@ python -m pytest
|
|
|
610
610
|
The current tree validates at:
|
|
611
611
|
|
|
612
612
|
```text
|
|
613
|
-
|
|
613
|
+
507 passed
|
|
614
614
|
```
|
|
615
615
|
|
|
616
616
|
Build distributions:
|
|
@@ -624,6 +624,7 @@ python -m twine check dist/*
|
|
|
624
624
|
|
|
625
625
|
## Recent releases
|
|
626
626
|
|
|
627
|
+
- `0.1.24`: add slash-command lifecycle events and TUI progress/result rendering so commands such as `/compact` and `/help` show running, done, failed, result, and error feedback instead of going silent.
|
|
627
628
|
- `0.1.23`: add SDK event streaming with `Agent.stream()`, root `Agent.add_plugin()`, a faster README flow, the `~/.codex-agent` runtime default, text-first voice defaults, safer prompt templating limited to system messages, smarter `codex-agent` local-server attachment, and clearer TUI tool-call progress lines.
|
|
628
629
|
- `0.1.22`: harden FastAPI/SSE and TUI streaming after a stabilization audit; add finite SSE read timeouts, stale-subscriber shutdown, persisted turn-event locking, synchronous replay delta flushes, process-runtime startup guards, isolated event handlers, and shared version metadata.
|
|
629
630
|
- `0.1.21`: add structured `AgentTurn` returns with messages/events, resumable non-terminal turns for `auto_proceed=False`, profile-based subagents, hierarchical message/event types, and internal wakeups as developer messages.
|
|
@@ -561,7 +561,7 @@ python -m pytest
|
|
|
561
561
|
The current tree validates at:
|
|
562
562
|
|
|
563
563
|
```text
|
|
564
|
-
|
|
564
|
+
507 passed
|
|
565
565
|
```
|
|
566
566
|
|
|
567
567
|
Build distributions:
|
|
@@ -575,6 +575,7 @@ python -m twine check dist/*
|
|
|
575
575
|
|
|
576
576
|
## Recent releases
|
|
577
577
|
|
|
578
|
+
- `0.1.24`: add slash-command lifecycle events and TUI progress/result rendering so commands such as `/compact` and `/help` show running, done, failed, result, and error feedback instead of going silent.
|
|
578
579
|
- `0.1.23`: add SDK event streaming with `Agent.stream()`, root `Agent.add_plugin()`, a faster README flow, the `~/.codex-agent` runtime default, text-first voice defaults, safer prompt templating limited to system messages, smarter `codex-agent` local-server attachment, and clearer TUI tool-call progress lines.
|
|
579
580
|
- `0.1.22`: harden FastAPI/SSE and TUI streaming after a stabilization audit; add finite SSE read timeouts, stale-subscriber shutdown, persisted turn-event locking, synchronous replay delta flushes, process-runtime startup guards, isolated event handlers, and shared version metadata.
|
|
580
581
|
- `0.1.21`: add structured `AgentTurn` returns with messages/events, resumable non-terminal turns for `auto_proceed=False`, profile-based subagents, hierarchical message/event types, and internal wakeups as developer messages.
|
|
@@ -27,6 +27,8 @@ from .event import (
|
|
|
27
27
|
ResponseToolCallsDeltaEvent,
|
|
28
28
|
ServerToolCallEvent,
|
|
29
29
|
SessionDeletedEvent,
|
|
30
|
+
SlashCommandDoneEvent,
|
|
31
|
+
SlashCommandStartEvent,
|
|
30
32
|
SubagentEvent,
|
|
31
33
|
SessionLoadedEvent,
|
|
32
34
|
ToolCallDoneEvent,
|
|
@@ -90,6 +92,8 @@ __all__ = [
|
|
|
90
92
|
"ResponseToolCallsDeltaEvent",
|
|
91
93
|
"ServerToolCallEvent",
|
|
92
94
|
"SessionDeletedEvent",
|
|
95
|
+
"SlashCommandDoneEvent",
|
|
96
|
+
"SlashCommandStartEvent",
|
|
93
97
|
"SubagentEvent",
|
|
94
98
|
"SessionLoadedEvent",
|
|
95
99
|
"ToolCallDoneEvent",
|
|
@@ -259,22 +259,35 @@ class CommandManager:
|
|
|
259
259
|
return isinstance(prompt, str) and prompt.strip().startswith("/")
|
|
260
260
|
|
|
261
261
|
def run_turn(self, prompt):
|
|
262
|
-
from .event import AssistantTurnEndEvent, AssistantTurnErrorEvent, AssistantTurnStartEvent
|
|
262
|
+
from .event import AssistantTurnEndEvent, AssistantTurnErrorEvent, AssistantTurnStartEvent, SlashCommandDoneEvent, SlashCommandStartEvent
|
|
263
263
|
|
|
264
|
+
name, args = self.parse_prompt(prompt)
|
|
264
265
|
self.agent.emit(AssistantTurnStartEvent, prompt=prompt)
|
|
266
|
+
self.agent.emit(SlashCommandStartEvent, prompt=prompt, name=name, args=args)
|
|
265
267
|
try:
|
|
266
|
-
result = self.
|
|
268
|
+
result = self.run_parsed(prompt, name=name, args=args)
|
|
269
|
+
self.agent.emit(SlashCommandDoneEvent, prompt=prompt, name=name, args=args, status="done", result=result)
|
|
267
270
|
self.agent.emit(AssistantTurnEndEvent, prompt=prompt, reason=TURN_REASON_COMPLETED)
|
|
268
271
|
return result
|
|
269
272
|
except Exception as exc:
|
|
273
|
+
self.agent.emit(SlashCommandDoneEvent, prompt=prompt, name=name, args=args, status="failed", error=str(exc))
|
|
270
274
|
self.agent.emit(AssistantTurnErrorEvent, prompt=prompt, error=str(exc))
|
|
271
275
|
raise
|
|
272
276
|
|
|
273
277
|
def run(self, prompt):
|
|
278
|
+
name, args = self.parse_prompt(prompt)
|
|
279
|
+
return self.run_parsed(prompt, name=name, args=args)
|
|
280
|
+
|
|
281
|
+
def parse_prompt(self, prompt):
|
|
282
|
+
text = str(prompt or "").strip()
|
|
283
|
+
name, _, args = text[1:].partition(" ") if text.startswith("/") else text.partition(" ")
|
|
284
|
+
return name, args
|
|
285
|
+
|
|
286
|
+
def run_parsed(self, prompt, name=None, args=None):
|
|
274
287
|
from .tool import reset_current_agent, set_current_agent
|
|
275
288
|
|
|
276
|
-
|
|
277
|
-
|
|
289
|
+
name = "" if name is None else name
|
|
290
|
+
args = "" if args is None else args
|
|
278
291
|
if not name:
|
|
279
292
|
return None
|
|
280
293
|
command_func = self.commands.get(name)
|
|
@@ -107,6 +107,21 @@ class MessageSubmittedEvent(Event):
|
|
|
107
107
|
turn_id = None
|
|
108
108
|
|
|
109
109
|
|
|
110
|
+
class SlashCommandStartEvent(Event):
|
|
111
|
+
prompt = ''
|
|
112
|
+
name = ''
|
|
113
|
+
args = ''
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class SlashCommandDoneEvent(Event):
|
|
117
|
+
prompt = ''
|
|
118
|
+
name = ''
|
|
119
|
+
args = ''
|
|
120
|
+
status = ''
|
|
121
|
+
result = None
|
|
122
|
+
error = ''
|
|
123
|
+
|
|
124
|
+
|
|
110
125
|
class CompactionRequestedEvent(Event):
|
|
111
126
|
compacted_message_count = 0
|
|
112
127
|
compacted_token_count = 0
|
|
@@ -16,6 +16,8 @@ from ..event import (
|
|
|
16
16
|
ResponseContentDeltaEvent,
|
|
17
17
|
ResponseDoneEvent,
|
|
18
18
|
ServerToolCallEvent,
|
|
19
|
+
SlashCommandDoneEvent,
|
|
20
|
+
SlashCommandStartEvent,
|
|
19
21
|
ToolCallDoneEvent,
|
|
20
22
|
ToolCallStartEvent,
|
|
21
23
|
)
|
|
@@ -116,6 +118,7 @@ class Chat:
|
|
|
116
118
|
self.client = client or AgentClient(base_url=base_url)
|
|
117
119
|
self.app = None
|
|
118
120
|
self._seen_tool_calls = set()
|
|
121
|
+
self._seen_slash_commands = set()
|
|
119
122
|
self._agent_buffer = ""
|
|
120
123
|
self._step_had_content = False
|
|
121
124
|
self._transcript = []
|
|
@@ -148,6 +151,8 @@ class Chat:
|
|
|
148
151
|
self.client.on(ToolCallStartEvent, self._once(self._on_tool_call_start))
|
|
149
152
|
self.client.on(ToolCallDoneEvent, self._once(self._on_tool_call_done))
|
|
150
153
|
self.client.on(ServerToolCallEvent, self._once(self._on_server_tool_call))
|
|
154
|
+
self.client.on(SlashCommandStartEvent, self._once(self._on_slash_command_start))
|
|
155
|
+
self.client.on(SlashCommandDoneEvent, self._once(self._on_slash_command_done))
|
|
151
156
|
self.client.on("server_connected", self._on_server_connected)
|
|
152
157
|
self.client.on("server_offline", self._on_server_offline)
|
|
153
158
|
self.client.on("server_subscribed", self._on_server_subscribed)
|
|
@@ -329,6 +334,9 @@ class Chat:
|
|
|
329
334
|
elif kind == "tool":
|
|
330
335
|
_, name, call_id, status = entry
|
|
331
336
|
log.write(chat.tool_call_renderable(name, call_id=call_id, status=status))
|
|
337
|
+
elif kind == "slash_command":
|
|
338
|
+
_, name, args, status = entry
|
|
339
|
+
log.write(chat.slash_command_renderable(name, args=args, status=status))
|
|
332
340
|
elif kind == "step_end":
|
|
333
341
|
log.write(self.qed_marker())
|
|
334
342
|
elif kind == "user_end":
|
|
@@ -350,6 +358,7 @@ class Chat:
|
|
|
350
358
|
chat._agent_buffer = ""
|
|
351
359
|
chat._step_had_content = False
|
|
352
360
|
chat._seen_tool_calls.clear()
|
|
361
|
+
chat._seen_slash_commands.clear()
|
|
353
362
|
|
|
354
363
|
def start_agent_step(self):
|
|
355
364
|
chat._step_had_content = False
|
|
@@ -365,6 +374,10 @@ class Chat:
|
|
|
365
374
|
chat.append_tool_transcript(name, call_id=call_id, status=status)
|
|
366
375
|
self.render_transcript()
|
|
367
376
|
|
|
377
|
+
def write_slash_command(self, name, args="", status=None):
|
|
378
|
+
chat.append_slash_command_transcript(name, args=args, status=status)
|
|
379
|
+
self.render_transcript()
|
|
380
|
+
|
|
368
381
|
def end_agent_step(self):
|
|
369
382
|
had_content = bool(chat._agent_buffer or chat._step_had_content)
|
|
370
383
|
if chat._transcript and chat._transcript[-1][0] == "agent":
|
|
@@ -420,6 +433,11 @@ class Chat:
|
|
|
420
433
|
self._transcript.append(("tool", name, call_id, status))
|
|
421
434
|
self._step_had_content = True
|
|
422
435
|
|
|
436
|
+
def append_slash_command_transcript(self, name, args="", status=None):
|
|
437
|
+
self.ensure_agent_transcript()
|
|
438
|
+
self._transcript.append(("slash_command", name, args, status))
|
|
439
|
+
self._step_had_content = True
|
|
440
|
+
|
|
423
441
|
@staticmethod
|
|
424
442
|
def normalized_tool_status(status):
|
|
425
443
|
if status in {"done", "completed", "success"}:
|
|
@@ -471,6 +489,25 @@ class Chat:
|
|
|
471
489
|
def tool_status_label(status):
|
|
472
490
|
return f" · {Chat.normalized_tool_status(status)}"
|
|
473
491
|
|
|
492
|
+
@staticmethod
|
|
493
|
+
def slash_command_renderable(name, args="", status=None):
|
|
494
|
+
icon, style = Chat.tool_status_badge(status)
|
|
495
|
+
command = f"/{name}" if name else "/"
|
|
496
|
+
text = Text(" ", style="#4b5563")
|
|
497
|
+
text += Text(f"{icon} ", style=style)
|
|
498
|
+
text += Text(command, style="bold white")
|
|
499
|
+
if args:
|
|
500
|
+
text += Text(f" {args}", style="#8b949e")
|
|
501
|
+
return text
|
|
502
|
+
|
|
503
|
+
@staticmethod
|
|
504
|
+
def slash_command_result_text(result):
|
|
505
|
+
if result is None:
|
|
506
|
+
return ""
|
|
507
|
+
if isinstance(result, str):
|
|
508
|
+
return result
|
|
509
|
+
return str(result)
|
|
510
|
+
|
|
474
511
|
def status_renderable(self):
|
|
475
512
|
if not self._server_connected:
|
|
476
513
|
suffix = f" · {self._server_error}" if self._server_error else ""
|
|
@@ -697,6 +734,30 @@ class Chat:
|
|
|
697
734
|
self._seen_tool_calls.add(key)
|
|
698
735
|
self._call_app("write_tool", name, call_id, status or "running")
|
|
699
736
|
|
|
737
|
+
def _on_slash_command_start(self, event):
|
|
738
|
+
self._mark_server_connected()
|
|
739
|
+
self.flush_agent_delta()
|
|
740
|
+
self._print_slash_command(event.get("name") or "", event.get("args") or "", status="running")
|
|
741
|
+
|
|
742
|
+
def _on_slash_command_done(self, event):
|
|
743
|
+
self._mark_server_connected()
|
|
744
|
+
self.flush_agent_delta()
|
|
745
|
+
status = event.get("status") or ("failed" if event.get("error") else "done")
|
|
746
|
+
self._print_slash_command(event.get("name") or "", event.get("args") or "", status=status)
|
|
747
|
+
result = self.slash_command_result_text(event.get("result"))
|
|
748
|
+
if result:
|
|
749
|
+
self._call_app("write_agent_delta", result)
|
|
750
|
+
if event.get("error"):
|
|
751
|
+
self._call_app("write_error", event.get("error"))
|
|
752
|
+
self._call_app("refresh_status")
|
|
753
|
+
|
|
754
|
+
def _print_slash_command(self, name, args="", status=None):
|
|
755
|
+
key = (name, args, status or "running")
|
|
756
|
+
if key in self._seen_slash_commands:
|
|
757
|
+
return
|
|
758
|
+
self._seen_slash_commands.add(key)
|
|
759
|
+
self._call_app("write_slash_command", name, args, status or "running")
|
|
760
|
+
|
|
700
761
|
def _on_turn_error(self, event):
|
|
701
762
|
self._call_app("write_error", event.get("error", "turn error"))
|
|
702
763
|
self._call_app("refresh_status")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.24"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-agent-framework
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.24
|
|
4
4
|
Summary: A lightweight event-driven Codex agent runtime.
|
|
5
5
|
Author: Baptiste
|
|
6
6
|
License-Expression: MIT
|
|
@@ -610,7 +610,7 @@ python -m pytest
|
|
|
610
610
|
The current tree validates at:
|
|
611
611
|
|
|
612
612
|
```text
|
|
613
|
-
|
|
613
|
+
507 passed
|
|
614
614
|
```
|
|
615
615
|
|
|
616
616
|
Build distributions:
|
|
@@ -624,6 +624,7 @@ python -m twine check dist/*
|
|
|
624
624
|
|
|
625
625
|
## Recent releases
|
|
626
626
|
|
|
627
|
+
- `0.1.24`: add slash-command lifecycle events and TUI progress/result rendering so commands such as `/compact` and `/help` show running, done, failed, result, and error feedback instead of going silent.
|
|
627
628
|
- `0.1.23`: add SDK event streaming with `Agent.stream()`, root `Agent.add_plugin()`, a faster README flow, the `~/.codex-agent` runtime default, text-first voice defaults, safer prompt templating limited to system messages, smarter `codex-agent` local-server attachment, and clearer TUI tool-call progress lines.
|
|
628
629
|
- `0.1.22`: harden FastAPI/SSE and TUI streaming after a stabilization audit; add finite SSE read timeouts, stale-subscriber shutdown, persisted turn-event locking, synchronous replay delta flushes, process-runtime startup guards, isolated event handlers, and shared version metadata.
|
|
629
630
|
- `0.1.21`: add structured `AgentTurn` returns with messages/events, resumable non-terminal turns for `auto_proceed=False`, profile-based subagents, hierarchical message/event types, and internal wakeups as developer messages.
|
|
@@ -7,7 +7,7 @@ from typing import Literal
|
|
|
7
7
|
|
|
8
8
|
import codex_agent.utils as utils
|
|
9
9
|
from modict import modict
|
|
10
|
-
from codex_agent import Agent, AssistantInterruptRequestedEvent, AssistantInterrupted, AssistantInterruptedEvent, AssistantTurnEndEvent, AssistantTurnStartEvent, AssistantStepEndEvent, AudioPlaybackEvent, MessageSubmittedEvent, CompactionCompletedEvent, CompactionMessage, CompactionRequestedEvent, ImageGenerationTool, ResponseContentDeltaEvent, ResponseDoneEvent, ServerToolCallEvent, SessionDeletedEvent, SessionLoadedEvent, SubagentEvent, ToolCallStartEvent, WebSearchTool, get_agent, get_current_call_id
|
|
10
|
+
from codex_agent import Agent, AssistantInterruptRequestedEvent, AssistantInterrupted, AssistantInterruptedEvent, AssistantTurnEndEvent, AssistantTurnStartEvent, AssistantStepEndEvent, AudioPlaybackEvent, MessageSubmittedEvent, CompactionCompletedEvent, CompactionMessage, CompactionRequestedEvent, ImageGenerationTool, ResponseContentDeltaEvent, ResponseDoneEvent, ServerToolCallEvent, SessionDeletedEvent, SessionLoadedEvent, SlashCommandDoneEvent, SlashCommandStartEvent, SubagentEvent, ToolCallStartEvent, WebSearchTool, get_agent, get_current_call_id
|
|
11
11
|
from codex_agent.sessions import AgentSession, UNRESOLVED_TOOL_RESULT_CONTENT
|
|
12
12
|
from codex_agent.image import ImageMessage
|
|
13
13
|
from codex_agent.message import AssistantMessage, DeveloperMessage, InterruptMessage, MessageChunk, ProviderMessage, ServerToolResponseMessage, SystemMessage, ToolResultMessage, ToolResultWrapper, UserMessage
|
|
@@ -578,6 +578,48 @@ def test_agent_state_mutations_run_through_mainloop_when_started(tmp_path, monke
|
|
|
578
578
|
agent.stop()
|
|
579
579
|
|
|
580
580
|
|
|
581
|
+
def test_slash_command_emits_start_and_done_events():
|
|
582
|
+
agent = Agent(session="new", voice_enabled=False)
|
|
583
|
+
seen = []
|
|
584
|
+
|
|
585
|
+
@agent.add_command
|
|
586
|
+
def echo(value="ok"):
|
|
587
|
+
return value
|
|
588
|
+
|
|
589
|
+
agent.on(SlashCommandStartEvent, lambda event: seen.append((event.type, event.name, event.args, None)))
|
|
590
|
+
agent.on(SlashCommandDoneEvent, lambda event: seen.append((event.type, event.name, event.args, event.status, event.result, event.error)))
|
|
591
|
+
|
|
592
|
+
turn = agent.submit_command("/echo hello").wait()
|
|
593
|
+
|
|
594
|
+
assert turn.result == "hello"
|
|
595
|
+
assert seen == [
|
|
596
|
+
("slash_command_start_event", "echo", "hello", None),
|
|
597
|
+
("slash_command_done_event", "echo", "hello", "done", "hello", ""),
|
|
598
|
+
]
|
|
599
|
+
assert [event.type for event in turn.events if event.type.startswith("slash_command_")] == [
|
|
600
|
+
"slash_command_start_event",
|
|
601
|
+
"slash_command_done_event",
|
|
602
|
+
]
|
|
603
|
+
agent.stop()
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def test_slash_command_emits_failed_done_event_on_error():
|
|
607
|
+
agent = Agent(session="new", voice_enabled=False)
|
|
608
|
+
seen = []
|
|
609
|
+
|
|
610
|
+
@agent.add_command
|
|
611
|
+
def fail():
|
|
612
|
+
raise RuntimeError("boom")
|
|
613
|
+
|
|
614
|
+
agent.on(SlashCommandDoneEvent, lambda event: seen.append((event.name, event.status, event.error)))
|
|
615
|
+
|
|
616
|
+
with pytest.raises(RuntimeError, match="boom"):
|
|
617
|
+
agent.submit_command("/fail").wait()
|
|
618
|
+
|
|
619
|
+
assert seen == [("fail", "failed", "boom")]
|
|
620
|
+
agent.stop()
|
|
621
|
+
|
|
622
|
+
|
|
581
623
|
def test_agent_interrupt_emits_request_event():
|
|
582
624
|
agent = Agent(session="new", voice_enabled=False)
|
|
583
625
|
seen = []
|
|
@@ -399,6 +399,47 @@ def test_chat_displays_tool_start_and_done_as_distinct_lines(monkeypatch):
|
|
|
399
399
|
assert calls.index(("write_tool", ("read", "call_read", "running"))) < calls.index(("write_tool", ("read", "call_read", "done")))
|
|
400
400
|
|
|
401
401
|
|
|
402
|
+
def test_chat_displays_slash_command_start_and_done_as_distinct_lines(monkeypatch):
|
|
403
|
+
chat = Chat()
|
|
404
|
+
calls = []
|
|
405
|
+
monkeypatch.setattr(chat, "_call_app", lambda method, *args: calls.append((method, args)))
|
|
406
|
+
|
|
407
|
+
chat.attach()
|
|
408
|
+
chat.client.emit_local(modict(type="assistant_turn_start_event"))
|
|
409
|
+
chat.client.emit_local(modict(type="slash_command_start_event", name="compact", args="--force"))
|
|
410
|
+
chat.client.emit_local(modict(type="slash_command_done_event", name="compact", args="--force", status="done"))
|
|
411
|
+
|
|
412
|
+
assert calls.count(("write_slash_command", ("compact", "--force", "running"))) == 1
|
|
413
|
+
assert calls.count(("write_slash_command", ("compact", "--force", "done"))) == 1
|
|
414
|
+
assert calls.index(("write_slash_command", ("compact", "--force", "running"))) < calls.index(("write_slash_command", ("compact", "--force", "done")))
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def test_chat_slash_command_done_displays_result(monkeypatch):
|
|
418
|
+
chat = Chat()
|
|
419
|
+
calls = []
|
|
420
|
+
monkeypatch.setattr(chat, "_call_app", lambda method, *args: calls.append((method, args)))
|
|
421
|
+
|
|
422
|
+
chat.attach()
|
|
423
|
+
chat.client.emit_local(modict(type="slash_command_done_event", name="help", status="done", result="Available commands: /help"))
|
|
424
|
+
|
|
425
|
+
assert ("write_slash_command", ("help", "", "done")) in calls
|
|
426
|
+
assert ("write_agent_delta", ("Available commands: /help",)) in calls
|
|
427
|
+
assert calls[-1] == ("refresh_status", ())
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def test_chat_slash_command_done_displays_errors(monkeypatch):
|
|
431
|
+
chat = Chat()
|
|
432
|
+
calls = []
|
|
433
|
+
monkeypatch.setattr(chat, "_call_app", lambda method, *args: calls.append((method, args)))
|
|
434
|
+
|
|
435
|
+
chat.attach()
|
|
436
|
+
chat.client.emit_local(modict(type="slash_command_done_event", name="missing", status="failed", error="Unknown command: /missing"))
|
|
437
|
+
|
|
438
|
+
assert ("write_slash_command", ("missing", "", "failed")) in calls
|
|
439
|
+
assert ("write_error", ("Unknown command: /missing",)) in calls
|
|
440
|
+
assert calls[-1] == ("refresh_status", ())
|
|
441
|
+
|
|
442
|
+
|
|
402
443
|
def test_chat_tool_status_helpers_are_compact_and_consistent():
|
|
403
444
|
assert Chat.normalized_tool_status("success") == "done"
|
|
404
445
|
assert Chat.normalized_tool_status("error") == "failed"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.23"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_commands.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/get_text/__init__.py
RENAMED
|
File without changes
|
{codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/get_text/default_gitignore
RENAMED
|
File without changes
|
{codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/get_text/get_text.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/prompts/system_prompt.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/server/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_get_text_browser.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|