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.
Files changed (109) hide show
  1. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/CHANGELOG.md +11 -0
  2. {codex_agent_framework-0.1.23/codex_agent_framework.egg-info → codex_agent_framework-0.1.24}/PKG-INFO +3 -2
  3. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/README.md +2 -1
  4. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/__init__.py +4 -0
  5. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/command.py +17 -4
  6. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/event.py +15 -0
  7. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/tui/chat.py +61 -0
  8. codex_agent_framework-0.1.24/codex_agent/version.py +1 -0
  9. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24/codex_agent_framework.egg-info}/PKG-INFO +3 -2
  10. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/pyproject.toml +1 -1
  11. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_agent.py +43 -1
  12. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_chat.py +41 -0
  13. codex_agent_framework-0.1.23/codex_agent/version.py +0 -1
  14. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/LICENSE +0 -0
  15. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/MANIFEST.in +0 -0
  16. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/__main__.py +0 -0
  17. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/agent.py +0 -0
  18. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/agent_runtime.py +0 -0
  19. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/ai.py +0 -0
  20. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/browser.py +0 -0
  21. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_commands.py +0 -0
  22. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/__init__.py +0 -0
  23. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/bash/__init__.py +0 -0
  24. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/browser/__init__.py +0 -0
  25. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/content/__init__.py +0 -0
  26. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/content/system.py +0 -0
  27. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/content/vision.py +0 -0
  28. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/desktop/__init__.py +0 -0
  29. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/environment/__init__.py +0 -0
  30. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/files/__init__.py +0 -0
  31. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/files/tools.py +0 -0
  32. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/interface/__init__.py +0 -0
  33. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/memory/__init__.py +0 -0
  34. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/planner/__init__.py +0 -0
  35. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/python/__init__.py +0 -0
  36. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/scheduler/__init__.py +0 -0
  37. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/subagents/__init__.py +0 -0
  38. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/builtin_plugins/subagents/profiles/explorer.json +0 -0
  39. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/cli/__init__.py +0 -0
  40. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/cli/headless.py +0 -0
  41. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/cli/main.py +0 -0
  42. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/cli/output.py +0 -0
  43. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/cli/root.py +0 -0
  44. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/cli/runner.py +0 -0
  45. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/client.py +0 -0
  46. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/config.py +0 -0
  47. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/context.py +0 -0
  48. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/desktop.py +0 -0
  49. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/get_text/__init__.py +0 -0
  50. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/get_text/default_gitignore +0 -0
  51. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/get_text/get_text.py +0 -0
  52. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/get_text/simpler_get_text.py +0 -0
  53. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/hooks.py +0 -0
  54. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/image.py +0 -0
  55. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/latex.py +0 -0
  56. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/mainloop.py +0 -0
  57. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/message.py +0 -0
  58. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/plugin.py +0 -0
  59. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/prompts/image_generation_system_prompt.txt +0 -0
  60. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/prompts/system_prompt.txt +0 -0
  61. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/provider.py +0 -0
  62. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/runtime.py +0 -0
  63. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/scheduler.py +0 -0
  64. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/scripts/install-system-dependencies.sh +0 -0
  65. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/server/__init__.py +0 -0
  66. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/server/app.py +0 -0
  67. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/server/core.py +0 -0
  68. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/service.py +0 -0
  69. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/sessions.py +0 -0
  70. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/status.py +0 -0
  71. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/stream.py +0 -0
  72. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/stream_utils.py +0 -0
  73. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/tool.py +0 -0
  74. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/tray.py +0 -0
  75. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/tui/__init__.py +0 -0
  76. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/tui/lifecycle.py +0 -0
  77. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/utils.py +0 -0
  78. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/voice.py +0 -0
  79. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent/worker.py +0 -0
  80. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent_framework.egg-info/SOURCES.txt +0 -0
  81. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
  82. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent_framework.egg-info/entry_points.txt +0 -0
  83. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent_framework.egg-info/requires.txt +0 -0
  84. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/codex_agent_framework.egg-info/top_level.txt +0 -0
  85. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/dependencies.txt +0 -0
  86. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/setup.cfg +0 -0
  87. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_agent_runtime.py +0 -0
  88. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_ai.py +0 -0
  89. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_browser.py +0 -0
  90. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_builtin_config.py +0 -0
  91. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_cli.py +0 -0
  92. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_cli_headless.py +0 -0
  93. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_cli_root.py +0 -0
  94. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_client.py +0 -0
  95. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_events.py +0 -0
  96. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_get_text_browser.py +0 -0
  97. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_hooks.py +0 -0
  98. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_image_message.py +0 -0
  99. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_local_desktop.py +0 -0
  100. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_memory.py +0 -0
  101. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_messages.py +0 -0
  102. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_planner.py +0 -0
  103. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_scheduler.py +0 -0
  104. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_server.py +0 -0
  105. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_service.py +0 -0
  106. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_tray.py +0 -0
  107. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_tui.py +0 -0
  108. {codex_agent_framework-0.1.23 → codex_agent_framework-0.1.24}/tests/test_utils.py +0 -0
  109. {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.23
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
- 489 passed
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
- 489 passed
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.run(prompt)
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
- text = prompt.strip()
277
- name, _, args = text[1:].partition(" ")
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.23
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
- 489 passed
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.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codex-agent-framework"
7
- version = "0.1.23"
7
+ version = "0.1.24"
8
8
  description = "A lightweight event-driven Codex agent runtime."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -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"