codex-agent-framework 0.1.16__tar.gz → 0.1.17__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 (105) hide show
  1. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/CHANGELOG.md +12 -0
  2. {codex_agent_framework-0.1.16/codex_agent_framework.egg-info → codex_agent_framework-0.1.17}/PKG-INFO +2 -1
  3. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/README.md +1 -0
  4. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/agent.py +1 -2
  5. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/cli/main.py +1 -1
  6. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/message.py +0 -1
  7. codex_agent_framework-0.1.17/codex_agent/server/__init__.py +29 -0
  8. codex_agent_framework-0.1.17/codex_agent/server/app.py +201 -0
  9. codex_agent_framework-0.1.16/codex_agent/server.py → codex_agent_framework-0.1.17/codex_agent/server/core.py +29 -208
  10. codex_agent_framework-0.1.17/codex_agent/tui/__init__.py +25 -0
  11. {codex_agent_framework-0.1.16/codex_agent → codex_agent_framework-0.1.17/codex_agent/tui}/chat.py +3 -3
  12. codex_agent_framework-0.1.16/codex_agent/tui.py → codex_agent_framework-0.1.17/codex_agent/tui/lifecycle.py +1 -1
  13. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17/codex_agent_framework.egg-info}/PKG-INFO +2 -1
  14. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent_framework.egg-info/SOURCES.txt +6 -3
  15. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/pyproject.toml +1 -1
  16. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_chat.py +2 -2
  17. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_server.py +21 -21
  18. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_tui.py +1 -1
  19. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/LICENSE +0 -0
  20. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/MANIFEST.in +0 -0
  21. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/__init__.py +0 -0
  22. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/__main__.py +0 -0
  23. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/agent_runtime.py +0 -0
  24. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/ai.py +0 -0
  25. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_commands.py +0 -0
  26. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/__init__.py +0 -0
  27. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/browser/__init__.py +0 -0
  28. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/browser/controller.py +0 -0
  29. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/desktop/__init__.py +0 -0
  30. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/desktop/controller.py +0 -0
  31. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/desktop/screenshot.py +0 -0
  32. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/memory/__init__.py +0 -0
  33. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/planner/__init__.py +0 -0
  34. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_plugins/scheduler/__init__.py +0 -0
  35. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_providers.py +0 -0
  36. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_tools/__init__.py +0 -0
  37. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_tools/files.py +0 -0
  38. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_tools/server_tools.py +0 -0
  39. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_tools/shell.py +0 -0
  40. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_tools/system.py +0 -0
  41. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/builtin_tools/vision.py +0 -0
  42. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/cli/__init__.py +0 -0
  43. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/cli/headless.py +0 -0
  44. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/cli/output.py +0 -0
  45. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/cli/root.py +0 -0
  46. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/cli/runner.py +0 -0
  47. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/client.py +0 -0
  48. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/command.py +0 -0
  49. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/config.py +0 -0
  50. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/context.py +0 -0
  51. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/event.py +0 -0
  52. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/get_text/__init__.py +0 -0
  53. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/get_text/default_gitignore +0 -0
  54. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/get_text/get_text.py +0 -0
  55. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/get_text/simpler_get_text.py +0 -0
  56. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/hooks.py +0 -0
  57. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/image.py +0 -0
  58. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/latex.py +0 -0
  59. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/mainloop.py +0 -0
  60. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/memory.py +0 -0
  61. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/plugin.py +0 -0
  62. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/prompts/image_generation_system_prompt.txt +0 -0
  63. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/prompts/system_prompt.txt +0 -0
  64. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/provider.py +0 -0
  65. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/registry.py +0 -0
  66. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/runtime.py +0 -0
  67. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/scheduler.py +0 -0
  68. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/scripts/install-system-dependencies.sh +0 -0
  69. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/service.py +0 -0
  70. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/sessions.py +0 -0
  71. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/status.py +0 -0
  72. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/stream_utils.py +0 -0
  73. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/tool.py +0 -0
  74. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/tray.py +0 -0
  75. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/utils.py +0 -0
  76. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/voice.py +0 -0
  77. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent/worker.py +0 -0
  78. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
  79. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent_framework.egg-info/entry_points.txt +0 -0
  80. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent_framework.egg-info/requires.txt +0 -0
  81. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/codex_agent_framework.egg-info/top_level.txt +0 -0
  82. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/dependencies.txt +0 -0
  83. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/setup.cfg +0 -0
  84. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_agent.py +0 -0
  85. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_agent_runtime.py +0 -0
  86. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_ai.py +0 -0
  87. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_browser.py +0 -0
  88. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_builtin_config.py +0 -0
  89. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_cli.py +0 -0
  90. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_cli_headless.py +0 -0
  91. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_cli_root.py +0 -0
  92. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_client.py +0 -0
  93. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_events.py +0 -0
  94. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_get_text_browser.py +0 -0
  95. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_hooks.py +0 -0
  96. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_image_message.py +0 -0
  97. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_local_desktop.py +0 -0
  98. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_memory.py +0 -0
  99. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_messages.py +0 -0
  100. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_planner.py +0 -0
  101. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_scheduler.py +0 -0
  102. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_service.py +0 -0
  103. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_tray.py +0 -0
  104. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_utils.py +0 -0
  105. {codex_agent_framework-0.1.16 → codex_agent_framework-0.1.17}/tests/test_worker.py +0 -0
@@ -4,6 +4,18 @@ 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.17] - 2026-05-08
8
+ ### Changed
9
+ - Reorganize the Textual TUI into the `codex_agent.tui` package, splitting chat UI code from TUI lifecycle helpers.
10
+ - Reorganize the FastAPI bridge into the `codex_agent.server` package, separating server core/service logic from app and route creation.
11
+ - Remove the obsolete `Message.lasting` field now that message visibility is controlled by `turns_left`, reducing serialized payload noise.
12
+
13
+ ### Removed
14
+ - Remove the old flat `codex_agent.chat` and `codex_agent.tui` modules in favor of the new package layout.
15
+
16
+ ### Tests
17
+ - Validate the full suite at 442 passing tests.
18
+
7
19
  ## [0.1.16] - 2026-05-08
8
20
  ### Fixed
9
21
  - Allow agent startup without an OpenAI API key by disabling OpenAI-dependent capabilities such as voice defaults and the built-in memory plugin instead of failing during initialization.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-agent-framework
3
- Version: 0.1.16
3
+ Version: 0.1.17
4
4
  Summary: A lightweight event-driven Codex agent runtime.
5
5
  Author: Baptiste
6
6
  License-Expression: MIT
@@ -442,6 +442,7 @@ python -m build
442
442
  The distribution includes prompt text files, `codex_agent/get_text/default_gitignore`, and the packaged Linux system dependency installer through package data and `MANIFEST.in`.
443
443
 
444
444
  ## Recent changes
445
+ - `0.1.17`: reorganize the TUI and FastAPI server into dedicated `codex_agent.tui` and `codex_agent.server` packages, remove obsolete message payload fields, and clean unused imports.
445
446
  - `0.1.16`: tolerate missing OpenAI API keys at startup by disabling OpenAI-dependent voice and memory features, and skip memory archiving when the memory plugin is unavailable.
446
447
  - `0.1.15`: add the `AgentRuntime` interface for server/CLI/process adapters, split the CLI into a package with headless runtime commands, add `codex-agent config set` plus `PATCH /config`, and make built-in tools/providers/plugins configurable.
447
448
  - `0.1.14`: add HookManager infrastructure, planner/scheduler robustness fixes, documented system dependencies, `codex-agent install-system-deps`, `codex-agent bootstrap`, and improved TUI SSE reconnect/replay handling.
@@ -393,6 +393,7 @@ python -m build
393
393
  The distribution includes prompt text files, `codex_agent/get_text/default_gitignore`, and the packaged Linux system dependency installer through package data and `MANIFEST.in`.
394
394
 
395
395
  ## Recent changes
396
+ - `0.1.17`: reorganize the TUI and FastAPI server into dedicated `codex_agent.tui` and `codex_agent.server` packages, remove obsolete message payload fields, and clean unused imports.
396
397
  - `0.1.16`: tolerate missing OpenAI API keys at startup by disabling OpenAI-dependent voice and memory features, and skip memory archiving when the memory plugin is unavailable.
397
398
  - `0.1.15`: add the `AgentRuntime` interface for server/CLI/process adapters, split the CLI into a package with headless runtime commands, add `codex-agent config set` plus `PATCH /config`, and make built-in tools/providers/plugins configurable.
398
399
  - `0.1.14`: add HookManager infrastructure, planner/scheduler robustness fixes, documented system dependencies, `codex-agent install-system-deps`, `codex-agent bootstrap`, and improved TUI SSE reconnect/replay handling.
@@ -1,5 +1,4 @@
1
1
  from .utils import text_content, ensure_runtime_dir, upload_file_to_folder, project_python_env, runtime_join
2
- from modict import modict
3
2
  import os
4
3
  import sys
5
4
  from .message import DeveloperMessage, ServerToolResponseMessage, SystemMessage, ToolResultMessage, UserMessage, message_from_data
@@ -432,5 +431,5 @@ class Agent:
432
431
  import os
433
432
  if os.environ.get('AGENT_ROOT_PATH') is None:
434
433
  set_root_path(file=__file__)
435
- from .chat import Chat
434
+ from .tui import Chat
436
435
  Chat(self).run()
@@ -2,7 +2,7 @@ from pathlib import Path
2
2
  import subprocess
3
3
 
4
4
  from codex_agent import Agent
5
- from codex_agent.chat import Chat
5
+ from codex_agent.tui import Chat
6
6
  from codex_agent.server import create_app
7
7
  from codex_agent.service import install_user_service, uninstall_user_service
8
8
  from codex_agent.tray import run_tray
@@ -32,7 +32,6 @@ class Message(modict):
32
32
  id = modict.factory(lambda: short_id())
33
33
  session_id = None
34
34
  timestamp = modict.factory(lambda: timestamp())
35
- lasting = 0
36
35
  turns_left = -1
37
36
  embedding = None
38
37
  estimated_token_count = modict.factory(dict)
@@ -0,0 +1,29 @@
1
+ """FastAPI server package for codex-agent."""
2
+
3
+ from .app import app, create_app, main
4
+ from .core import (
5
+ AgentServer,
6
+ SERVER_CONFIG_FILE,
7
+ SERVER_EVENTS_FILE,
8
+ create_server_agent,
9
+ load_server_config,
10
+ remembered_server_session,
11
+ save_server_config,
12
+ server_config_file,
13
+ server_events_file,
14
+ )
15
+
16
+ __all__ = [
17
+ "AgentServer",
18
+ "SERVER_CONFIG_FILE",
19
+ "SERVER_EVENTS_FILE",
20
+ "app",
21
+ "create_app",
22
+ "create_server_agent",
23
+ "load_server_config",
24
+ "main",
25
+ "remembered_server_session",
26
+ "save_server_config",
27
+ "server_config_file",
28
+ "server_events_file",
29
+ ]
@@ -0,0 +1,201 @@
1
+ import json
2
+
3
+ from ..agent import Agent
4
+ from ..agent_runtime import InProcessAgentRuntime, ProcessAgentRuntime
5
+ from .core import AgentServer, create_server_agent, remembered_server_session
6
+
7
+ def create_app(agent: Agent | None = None, start_agent=False, process_agent=False, **agent_kwargs):
8
+ from contextlib import asynccontextmanager
9
+
10
+ from fastapi import FastAPI, HTTPException, Request
11
+ from fastapi.responses import StreamingResponse
12
+ from pydantic import BaseModel
13
+
14
+ if process_agent and agent is None:
15
+ if "session" not in agent_kwargs:
16
+ agent_kwargs["session"] = remembered_server_session() or "latest"
17
+ runtime = ProcessAgentRuntime(agent_kwargs=agent_kwargs)
18
+ else:
19
+ runtime = InProcessAgentRuntime(agent or create_server_agent(**agent_kwargs))
20
+ service = AgentServer(runtime=runtime)
21
+ if start_agent:
22
+ service.start()
23
+
24
+ @asynccontextmanager
25
+ async def lifespan(app):
26
+ try:
27
+ yield
28
+ finally:
29
+ service.stop(timeout=1.0)
30
+
31
+ app = FastAPI(title="Codex Agent", version="0.1.17", lifespan=lifespan)
32
+ app.state.agent_service = service
33
+
34
+ class TurnRequest(BaseModel):
35
+ prompt: str
36
+
37
+ class InterruptRequest(BaseModel):
38
+ reason: str = "api"
39
+
40
+ class ConfigUpdateRequest(BaseModel):
41
+ values: dict = {}
42
+ save: bool = True
43
+
44
+ class WakeupRequest(BaseModel):
45
+ prompt: str
46
+ run_at: str | None = None
47
+ delay_seconds: float | None = None
48
+ interval_seconds: float | None = None
49
+ title: str = ""
50
+
51
+ class RestartRequest(BaseModel):
52
+ prompt: str | None = None
53
+ title: str = "post-restart wakeup"
54
+
55
+ def jsonable(value):
56
+ return json.loads(json.dumps(value, ensure_ascii=False, default=str))
57
+
58
+ def busy_guard(call):
59
+ try:
60
+ return call()
61
+ except RuntimeError as exc:
62
+ raise HTTPException(status_code=409, detail=str(exc)) from exc
63
+ except ValueError as exc:
64
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
65
+
66
+ @app.get("/health")
67
+ def health():
68
+ return {"ok": True, "busy": service.busy, "session_id": service.current_session_id}
69
+
70
+ @app.get("/status")
71
+ def status():
72
+ return jsonable(service.status())
73
+
74
+ @app.get("/config")
75
+ def config():
76
+ return jsonable(service.config())
77
+
78
+ @app.patch("/config")
79
+ def update_config(request: ConfigUpdateRequest):
80
+ return jsonable(busy_guard(lambda: service.update_config(request.values, save=request.save)))
81
+
82
+ @app.get("/session")
83
+ def session():
84
+ return jsonable(service.session())
85
+
86
+ @app.get("/sessions")
87
+ def sessions():
88
+ return jsonable(service.sessions())
89
+
90
+ @app.post("/sessions/new")
91
+ def start_new_session():
92
+ return jsonable(busy_guard(service.start_new_session))
93
+
94
+ @app.post("/sessions/{session_id}/load")
95
+ def load_session(session_id: str):
96
+ return jsonable(busy_guard(lambda: service.load_session(session_id)))
97
+
98
+ @app.delete("/sessions/{session_id}")
99
+ def delete_session(session_id: str):
100
+ return jsonable(busy_guard(lambda: service.delete_session(session_id)))
101
+
102
+ @app.post("/sessions/navigate/{direction}")
103
+ def navigate_session(direction: str):
104
+ return jsonable(busy_guard(lambda: service.navigate_session(direction)))
105
+
106
+ @app.get("/messages")
107
+ def messages():
108
+ return jsonable(service.messages())
109
+
110
+ @app.get("/memory")
111
+ def memory():
112
+ return jsonable(service.memory())
113
+
114
+ @app.get("/tools")
115
+ def tools():
116
+ return jsonable(service.tools())
117
+
118
+ @app.get("/tui")
119
+ def tui():
120
+ return jsonable(service.tui())
121
+
122
+ @app.post("/tui/open")
123
+ def open_tui(request: Request):
124
+ base_url = str(request.base_url).rstrip("/")
125
+ return jsonable(busy_guard(lambda: service.open_tui(base_url=base_url)))
126
+
127
+ @app.post("/tui/close")
128
+ def close_tui():
129
+ return jsonable(service.close_tui())
130
+
131
+ @app.post("/restart")
132
+ @app.post("/restart/")
133
+ def restart(request: RestartRequest):
134
+ return jsonable(busy_guard(lambda: service.restart(**request.model_dump())))
135
+
136
+ @app.get("/wakeups")
137
+ def wakeups(include_done: bool = True):
138
+ return jsonable(service.wakeups(include_done=include_done))
139
+
140
+ @app.post("/wakeups", status_code=201)
141
+ def schedule_wakeup(request: WakeupRequest):
142
+ return jsonable(busy_guard(lambda: service.schedule_wakeup(**request.model_dump())))
143
+
144
+ @app.post("/wakeups/{job_id}/cancel")
145
+ def cancel_wakeup(job_id: str):
146
+ return jsonable(busy_guard(lambda: service.cancel_wakeup(job_id)))
147
+
148
+ @app.delete("/wakeups/{job_id}")
149
+ def delete_wakeup(job_id: str):
150
+ return jsonable(busy_guard(lambda: service.delete_wakeup(job_id)))
151
+
152
+ @app.post("/turns", status_code=202)
153
+ def turns(request: TurnRequest):
154
+ result = busy_guard(lambda: service.submit_prompt(request.prompt))
155
+ return {"accepted": True, **jsonable(result)}
156
+
157
+ @app.post("/interrupt")
158
+ def interrupt(request: InterruptRequest):
159
+ return jsonable(service.interrupt(request.reason))
160
+
161
+ @app.get("/events")
162
+ def events(limit: int | None = None, after_sequence: int | None = None, client_pid: int | None = None):
163
+ try:
164
+ subscription = service.subscribe(after_sequence=after_sequence, client_pid=client_pid)
165
+ except RuntimeError as exc:
166
+ raise HTTPException(status_code=409, detail=str(exc)) from exc
167
+
168
+ def stream():
169
+ count = 0
170
+ for payload in subscription:
171
+ lines = []
172
+ if payload.get("id"):
173
+ lines.append(f"id: {payload.id}")
174
+ lines.append(f"event: {payload.get('type', 'message')}")
175
+ lines.append(f"data: {json.dumps(payload, ensure_ascii=False, default=str)}")
176
+ yield "\n".join(lines) + "\n\n"
177
+ count += 1
178
+ if limit is not None and count >= limit:
179
+ break
180
+ return StreamingResponse(stream(), media_type="text/event-stream")
181
+
182
+ @app.get("/events/replay")
183
+ def replay_events(before_sequence: int | None = None, session_id: str | None = None):
184
+ return jsonable(service.replay_events(before_sequence=before_sequence, session_id=session_id))
185
+
186
+ return app
187
+
188
+
189
+ app = None
190
+
191
+
192
+ def main():
193
+ import uvicorn
194
+
195
+ global app
196
+ app = create_app(start_agent=True, process_agent=True)
197
+ uvicorn.run(app, host="127.0.0.1", port=8765, timeout_graceful_shutdown=2)
198
+
199
+
200
+ if __name__ == "__main__":
201
+ main()
@@ -8,14 +8,14 @@ from collections import deque
8
8
 
9
9
  from modict import modict
10
10
 
11
- from .agent import Agent
12
- from .agent_runtime import AgentRuntime, InProcessAgentRuntime, ProcessAgentRuntime
13
- from .event import Event
14
- from .message import UserMessage
15
- from .sessions import AgentSession
16
- from .service import pop_restart_wakeup, restart_server_service, write_restart_wakeup
17
- from .utils import ensure_runtime_dir, runtime_join
18
- from .tui import clear_tui, current_tui, open_tui_terminal, terminate_tui
11
+ from ..agent import Agent
12
+ from ..agent_runtime import AgentRuntime
13
+ from ..event import Event
14
+ from ..message import UserMessage
15
+ from ..sessions import AgentSession
16
+ from ..service import pop_restart_wakeup, restart_server_service, write_restart_wakeup
17
+ from ..utils import ensure_runtime_dir, runtime_join
18
+ from ..tui import clear_tui, current_tui, open_tui_terminal, terminate_tui
19
19
 
20
20
 
21
21
 
@@ -266,6 +266,16 @@ class AgentServer:
266
266
  for subscriber, state in list(self.subscribers.items()):
267
267
  if not state.thread.is_alive():
268
268
  self.subscribers.pop(subscriber, None)
269
+ return len(self.subscribers)
270
+
271
+ def subscriber_count(self):
272
+ if not self.subscriber_lock.acquire(timeout=0.05):
273
+ return len(self.subscribers)
274
+ try:
275
+ self.prune_subscribers()
276
+ return len(self.subscribers)
277
+ finally:
278
+ self.subscriber_lock.release()
269
279
 
270
280
  def close_subscribers(self, reason="server shutdown"):
271
281
  self.shutdown_requested.set()
@@ -277,7 +287,9 @@ class AgentServer:
277
287
  def subscribe(self, after_sequence=None, client_pid=None):
278
288
  after_sequence = int(after_sequence) if after_sequence is not None else None
279
289
  client_pid = int(client_pid) if client_pid is not None else None
280
- with self.subscriber_lock:
290
+ if not self.subscriber_lock.acquire(timeout=1.0):
291
+ raise RuntimeError("event subscription manager is busy")
292
+ try:
281
293
  self.shutdown_requested.clear()
282
294
  self.prune_subscribers()
283
295
  tui = current_tui()
@@ -297,6 +309,8 @@ class AgentServer:
297
309
  and int(event.get("sequence") or 0) <= replay_before_sequence
298
310
  and self.event_belongs_to_session(event, session_id)
299
311
  ]
312
+ finally:
313
+ self.subscriber_lock.release()
300
314
  def stream():
301
315
  try:
302
316
  yield modict(
@@ -321,18 +335,20 @@ class AgentServer:
321
335
  if payload.get("type") == "server_shutdown":
322
336
  break
323
337
  finally:
324
- with self.subscriber_lock:
338
+ if not self.subscriber_lock.acquire(timeout=1.0):
339
+ return
340
+ try:
325
341
  self.subscribers.pop(subscriber, None)
342
+ finally:
343
+ self.subscriber_lock.release()
326
344
  return stream()
327
345
 
328
346
  def status(self):
329
- with self.subscriber_lock:
330
- self.prune_subscribers()
331
347
  status = self.runtime.get_status()
332
348
  status.update(
333
349
  busy=self.busy,
334
350
  current_turn_id=self.current_turn_id,
335
- subscribers=len(self.subscribers),
351
+ subscribers=self.subscriber_count(),
336
352
  event_backlog=len(self.events),
337
353
  )
338
354
  self.persist_current_session()
@@ -483,198 +499,3 @@ class AgentServer:
483
499
  raise RuntimeError("agent is busy")
484
500
 
485
501
 
486
- def create_app(agent: Agent | None = None, start_agent=False, process_agent=False, **agent_kwargs):
487
- from contextlib import asynccontextmanager
488
-
489
- from fastapi import FastAPI, HTTPException, Request
490
- from fastapi.responses import StreamingResponse
491
- from pydantic import BaseModel
492
-
493
- if process_agent and agent is None:
494
- if "session" not in agent_kwargs:
495
- agent_kwargs["session"] = remembered_server_session() or "latest"
496
- runtime = ProcessAgentRuntime(agent_kwargs=agent_kwargs)
497
- else:
498
- runtime = InProcessAgentRuntime(agent or create_server_agent(**agent_kwargs))
499
- service = AgentServer(runtime=runtime)
500
- if start_agent:
501
- service.start()
502
-
503
- @asynccontextmanager
504
- async def lifespan(app):
505
- try:
506
- yield
507
- finally:
508
- service.stop(timeout=1.0)
509
-
510
- app = FastAPI(title="Codex Agent", version="0.1.0", lifespan=lifespan)
511
- app.state.agent_service = service
512
-
513
- class TurnRequest(BaseModel):
514
- prompt: str
515
-
516
- class InterruptRequest(BaseModel):
517
- reason: str = "api"
518
-
519
- class ConfigUpdateRequest(BaseModel):
520
- values: dict = {}
521
- save: bool = True
522
-
523
- class WakeupRequest(BaseModel):
524
- prompt: str
525
- run_at: str | None = None
526
- delay_seconds: float | None = None
527
- interval_seconds: float | None = None
528
- title: str = ""
529
-
530
- class RestartRequest(BaseModel):
531
- prompt: str | None = None
532
- title: str = "post-restart wakeup"
533
-
534
- def jsonable(value):
535
- return json.loads(json.dumps(value, ensure_ascii=False, default=str))
536
-
537
- def busy_guard(call):
538
- try:
539
- return call()
540
- except RuntimeError as exc:
541
- raise HTTPException(status_code=409, detail=str(exc)) from exc
542
- except ValueError as exc:
543
- raise HTTPException(status_code=400, detail=str(exc)) from exc
544
-
545
- @app.get("/health")
546
- def health():
547
- return {"ok": True, "busy": service.busy, "session_id": service.current_session_id}
548
-
549
- @app.get("/status")
550
- def status():
551
- return jsonable(service.status())
552
-
553
- @app.get("/config")
554
- def config():
555
- return jsonable(service.config())
556
-
557
- @app.patch("/config")
558
- def update_config(request: ConfigUpdateRequest):
559
- return jsonable(busy_guard(lambda: service.update_config(request.values, save=request.save)))
560
-
561
- @app.get("/session")
562
- def session():
563
- return jsonable(service.session())
564
-
565
- @app.get("/sessions")
566
- def sessions():
567
- return jsonable(service.sessions())
568
-
569
- @app.post("/sessions/new")
570
- def start_new_session():
571
- return jsonable(busy_guard(service.start_new_session))
572
-
573
- @app.post("/sessions/{session_id}/load")
574
- def load_session(session_id: str):
575
- return jsonable(busy_guard(lambda: service.load_session(session_id)))
576
-
577
- @app.delete("/sessions/{session_id}")
578
- def delete_session(session_id: str):
579
- return jsonable(busy_guard(lambda: service.delete_session(session_id)))
580
-
581
- @app.post("/sessions/navigate/{direction}")
582
- def navigate_session(direction: str):
583
- return jsonable(busy_guard(lambda: service.navigate_session(direction)))
584
-
585
- @app.get("/messages")
586
- def messages():
587
- return jsonable(service.messages())
588
-
589
- @app.get("/memory")
590
- def memory():
591
- return jsonable(service.memory())
592
-
593
- @app.get("/tools")
594
- def tools():
595
- return jsonable(service.tools())
596
-
597
- @app.get("/tui")
598
- def tui():
599
- return jsonable(service.tui())
600
-
601
- @app.post("/tui/open")
602
- def open_tui(request: Request):
603
- base_url = str(request.base_url).rstrip("/")
604
- return jsonable(busy_guard(lambda: service.open_tui(base_url=base_url)))
605
-
606
- @app.post("/tui/close")
607
- def close_tui():
608
- return jsonable(service.close_tui())
609
-
610
- @app.post("/restart")
611
- @app.post("/restart/")
612
- def restart(request: RestartRequest):
613
- return jsonable(busy_guard(lambda: service.restart(**request.model_dump())))
614
-
615
- @app.get("/wakeups")
616
- def wakeups(include_done: bool = True):
617
- return jsonable(service.wakeups(include_done=include_done))
618
-
619
- @app.post("/wakeups", status_code=201)
620
- def schedule_wakeup(request: WakeupRequest):
621
- return jsonable(busy_guard(lambda: service.schedule_wakeup(**request.model_dump())))
622
-
623
- @app.post("/wakeups/{job_id}/cancel")
624
- def cancel_wakeup(job_id: str):
625
- return jsonable(busy_guard(lambda: service.cancel_wakeup(job_id)))
626
-
627
- @app.delete("/wakeups/{job_id}")
628
- def delete_wakeup(job_id: str):
629
- return jsonable(busy_guard(lambda: service.delete_wakeup(job_id)))
630
-
631
- @app.post("/turns", status_code=202)
632
- def turns(request: TurnRequest):
633
- result = busy_guard(lambda: service.submit_prompt(request.prompt))
634
- return {"accepted": True, **jsonable(result)}
635
-
636
- @app.post("/interrupt")
637
- def interrupt(request: InterruptRequest):
638
- return jsonable(service.interrupt(request.reason))
639
-
640
- @app.get("/events")
641
- def events(limit: int | None = None, after_sequence: int | None = None, client_pid: int | None = None):
642
- try:
643
- subscription = service.subscribe(after_sequence=after_sequence, client_pid=client_pid)
644
- except RuntimeError as exc:
645
- raise HTTPException(status_code=409, detail=str(exc)) from exc
646
-
647
- def stream():
648
- count = 0
649
- for payload in subscription:
650
- lines = []
651
- if payload.get("id"):
652
- lines.append(f"id: {payload.id}")
653
- lines.append(f"event: {payload.get('type', 'message')}")
654
- lines.append(f"data: {json.dumps(payload, ensure_ascii=False, default=str)}")
655
- yield "\n".join(lines) + "\n\n"
656
- count += 1
657
- if limit is not None and count >= limit:
658
- break
659
- return StreamingResponse(stream(), media_type="text/event-stream")
660
-
661
- @app.get("/events/replay")
662
- def replay_events(before_sequence: int | None = None, session_id: str | None = None):
663
- return jsonable(service.replay_events(before_sequence=before_sequence, session_id=session_id))
664
-
665
- return app
666
-
667
-
668
- app = None
669
-
670
-
671
- def main():
672
- import uvicorn
673
-
674
- global app
675
- app = create_app(start_agent=True, process_agent=True)
676
- uvicorn.run(app, host="127.0.0.1", port=8765, timeout_graceful_shutdown=2)
677
-
678
-
679
- if __name__ == "__main__":
680
- main()
@@ -0,0 +1,25 @@
1
+ """Textual TUI client package."""
2
+
3
+ from .chat import Chat
4
+ from .lifecycle import (
5
+ clear_tui,
6
+ current_tui,
7
+ install_tui_cleanup_handlers,
8
+ open_tui_terminal,
9
+ register_tui,
10
+ terminal_command_candidates,
11
+ terminal_process_kwargs,
12
+ terminate_tui,
13
+ )
14
+
15
+ __all__ = [
16
+ "Chat",
17
+ "clear_tui",
18
+ "current_tui",
19
+ "install_tui_cleanup_handlers",
20
+ "open_tui_terminal",
21
+ "register_tui",
22
+ "terminal_command_candidates",
23
+ "terminal_process_kwargs",
24
+ "terminate_tui",
25
+ ]
@@ -3,8 +3,8 @@ import sys
3
3
 
4
4
  from rich.text import Text
5
5
 
6
- from .client import AgentClient
7
- from .event import (
6
+ from ..client import AgentClient
7
+ from ..event import (
8
8
  AgentTurnStartEvent,
9
9
  AgentTurnEndEvent,
10
10
  AgentTurnErrorEvent,
@@ -18,7 +18,7 @@ from .event import (
18
18
  ServerToolCallEvent,
19
19
  ToolCallStartEvent,
20
20
  )
21
- from .tui import clear_tui, install_tui_cleanup_handlers, register_tui
21
+ from .lifecycle import clear_tui, install_tui_cleanup_handlers, register_tui
22
22
 
23
23
 
24
24
  class Chat:
@@ -8,7 +8,7 @@ import threading
8
8
 
9
9
  from modict import modict
10
10
 
11
- from .utils import runtime_join, timestamp
11
+ from ..utils import runtime_join, timestamp
12
12
 
13
13
 
14
14
  def tui_file():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-agent-framework
3
- Version: 0.1.16
3
+ Version: 0.1.17
4
4
  Summary: A lightweight event-driven Codex agent runtime.
5
5
  Author: Baptiste
6
6
  License-Expression: MIT
@@ -442,6 +442,7 @@ python -m build
442
442
  The distribution includes prompt text files, `codex_agent/get_text/default_gitignore`, and the packaged Linux system dependency installer through package data and `MANIFEST.in`.
443
443
 
444
444
  ## Recent changes
445
+ - `0.1.17`: reorganize the TUI and FastAPI server into dedicated `codex_agent.tui` and `codex_agent.server` packages, remove obsolete message payload fields, and clean unused imports.
445
446
  - `0.1.16`: tolerate missing OpenAI API keys at startup by disabling OpenAI-dependent voice and memory features, and skip memory archiving when the memory plugin is unavailable.
446
447
  - `0.1.15`: add the `AgentRuntime` interface for server/CLI/process adapters, split the CLI into a package with headless runtime commands, add `codex-agent config set` plus `PATCH /config`, and make built-in tools/providers/plugins configurable.
447
448
  - `0.1.14`: add HookManager infrastructure, planner/scheduler robustness fixes, documented system dependencies, `codex-agent install-system-deps`, `codex-agent bootstrap`, and improved TUI SSE reconnect/replay handling.
@@ -11,7 +11,6 @@ codex_agent/agent_runtime.py
11
11
  codex_agent/ai.py
12
12
  codex_agent/builtin_commands.py
13
13
  codex_agent/builtin_providers.py
14
- codex_agent/chat.py
15
14
  codex_agent/client.py
16
15
  codex_agent/command.py
17
16
  codex_agent/config.py
@@ -28,14 +27,12 @@ codex_agent/provider.py
28
27
  codex_agent/registry.py
29
28
  codex_agent/runtime.py
30
29
  codex_agent/scheduler.py
31
- codex_agent/server.py
32
30
  codex_agent/service.py
33
31
  codex_agent/sessions.py
34
32
  codex_agent/status.py
35
33
  codex_agent/stream_utils.py
36
34
  codex_agent/tool.py
37
35
  codex_agent/tray.py
38
- codex_agent/tui.py
39
36
  codex_agent/utils.py
40
37
  codex_agent/voice.py
41
38
  codex_agent/worker.py
@@ -67,6 +64,12 @@ codex_agent/get_text/simpler_get_text.py
67
64
  codex_agent/prompts/image_generation_system_prompt.txt
68
65
  codex_agent/prompts/system_prompt.txt
69
66
  codex_agent/scripts/install-system-dependencies.sh
67
+ codex_agent/server/__init__.py
68
+ codex_agent/server/app.py
69
+ codex_agent/server/core.py
70
+ codex_agent/tui/__init__.py
71
+ codex_agent/tui/chat.py
72
+ codex_agent/tui/lifecycle.py
70
73
  codex_agent_framework.egg-info/PKG-INFO
71
74
  codex_agent_framework.egg-info/SOURCES.txt
72
75
  codex_agent_framework.egg-info/dependency_links.txt
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codex-agent-framework"
7
- version = "0.1.16"
7
+ version = "0.1.17"
8
8
  description = "A lightweight event-driven Codex agent runtime."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,7 +1,7 @@
1
1
  from modict import modict
2
2
 
3
3
  from codex_agent import Agent
4
- from codex_agent.chat import Chat
4
+ from codex_agent.tui.chat import Chat
5
5
  from codex_agent.message import CompactionMessage, DeveloperMessage, UserMessage
6
6
 
7
7
 
@@ -308,7 +308,7 @@ def test_chat_replays_startup_events_after_subscription(monkeypatch):
308
308
 
309
309
  chat.app = App()
310
310
  chat.attach()
311
- monkeypatch.setattr("codex_agent.chat.register_tui", lambda base_url: None)
311
+ monkeypatch.setattr("codex_agent.tui.chat.register_tui", lambda base_url: None)
312
312
  monkeypatch.setattr(
313
313
  chat.client,
314
314
  "replay_events",
@@ -49,7 +49,7 @@ def test_agent_server_keeps_slow_subscribers_attached():
49
49
  def test_agent_server_refuses_second_event_subscriber(monkeypatch):
50
50
  agent = Agent(session="new", voice_enabled=False)
51
51
  service = AgentServer(InProcessAgentRuntime(agent))
52
- monkeypatch.setattr("codex_agent.server.current_tui", lambda: modict(pid=1234))
52
+ monkeypatch.setattr("codex_agent.server.core.current_tui", lambda: modict(pid=1234))
53
53
  stream = service.subscribe()
54
54
 
55
55
  next(stream)
@@ -318,9 +318,9 @@ def test_agent_server_open_tui_returns_registered_client(monkeypatch):
318
318
  service = AgentServer(InProcessAgentRuntime(agent))
319
319
  tui = modict(pid=4321, base_url="http://agent.local")
320
320
 
321
- monkeypatch.setattr("codex_agent.server.current_tui", lambda: tui)
321
+ monkeypatch.setattr("codex_agent.server.core.current_tui", lambda: tui)
322
322
  monkeypatch.setattr(
323
- "codex_agent.server.open_tui_terminal",
323
+ "codex_agent.server.core.open_tui_terminal",
324
324
  lambda **kwargs: (_ for _ in ()).throw(AssertionError("terminal should not be opened")),
325
325
  )
326
326
 
@@ -337,8 +337,8 @@ def test_agent_server_open_tui_launches_terminal_when_no_client(monkeypatch):
337
337
  class Process:
338
338
  pid = 9876
339
339
 
340
- monkeypatch.setattr("codex_agent.server.current_tui", lambda: None)
341
- monkeypatch.setattr("codex_agent.server.open_tui_terminal", lambda base_url: Process())
340
+ monkeypatch.setattr("codex_agent.server.core.current_tui", lambda: None)
341
+ monkeypatch.setattr("codex_agent.server.core.open_tui_terminal", lambda base_url: Process())
342
342
 
343
343
  result = service.open_tui(base_url="http://agent.local")
344
344
 
@@ -352,9 +352,9 @@ def test_agent_server_close_tui_terminates_registered_pid(monkeypatch):
352
352
  service = AgentServer(InProcessAgentRuntime(agent))
353
353
  calls = []
354
354
 
355
- monkeypatch.setattr("codex_agent.server.current_tui", lambda: modict(pid=2468))
356
- monkeypatch.setattr("codex_agent.server.terminate_tui", lambda pid: calls.append(("terminate", pid)))
357
- monkeypatch.setattr("codex_agent.server.clear_tui", lambda pid=None: calls.append(("clear", pid)))
355
+ monkeypatch.setattr("codex_agent.server.core.current_tui", lambda: modict(pid=2468))
356
+ monkeypatch.setattr("codex_agent.server.core.terminate_tui", lambda pid: calls.append(("terminate", pid)))
357
+ monkeypatch.setattr("codex_agent.server.core.clear_tui", lambda pid=None: calls.append(("clear", pid)))
358
358
 
359
359
  result = service.close_tui()
360
360
 
@@ -417,7 +417,7 @@ def test_agent_server_refuses_second_subscriber_when_tui_is_registered(monkeypat
417
417
  agent = Agent(session="new", voice_enabled=False)
418
418
  service = AgentServer(InProcessAgentRuntime(agent))
419
419
  service.subscribers[queue.Queue()] = modict(thread=threading.current_thread())
420
- monkeypatch.setattr("codex_agent.server.current_tui", lambda: modict(pid=1234))
420
+ monkeypatch.setattr("codex_agent.server.core.current_tui", lambda: modict(pid=1234))
421
421
 
422
422
  try:
423
423
  try:
@@ -434,7 +434,7 @@ def test_agent_server_replaces_stale_subscriber_for_same_tui_pid(monkeypatch):
434
434
  agent = Agent(session="new", voice_enabled=False)
435
435
  service = AgentServer(InProcessAgentRuntime(agent))
436
436
  service.subscribers[queue.Queue()] = modict(thread=threading.current_thread(), client_pid=1234)
437
- monkeypatch.setattr("codex_agent.server.current_tui", lambda: modict(pid=1234))
437
+ monkeypatch.setattr("codex_agent.server.core.current_tui", lambda: modict(pid=1234))
438
438
 
439
439
  stream = service.subscribe(client_pid=1234)
440
440
  try:
@@ -449,7 +449,7 @@ def test_agent_server_replaces_stale_subscriber_when_no_tui_is_registered(monkey
449
449
  agent = Agent(session="new", voice_enabled=False)
450
450
  service = AgentServer(InProcessAgentRuntime(agent))
451
451
  service.subscribers[queue.Queue()] = modict(thread=threading.current_thread())
452
- monkeypatch.setattr("codex_agent.server.current_tui", lambda: None)
452
+ monkeypatch.setattr("codex_agent.server.core.current_tui", lambda: None)
453
453
 
454
454
  stream = service.subscribe()
455
455
  try:
@@ -465,7 +465,7 @@ def test_create_app_refuses_second_sse_client(monkeypatch):
465
465
  client = TestClient(app)
466
466
  service = app.state.agent_service
467
467
  service.subscribers[queue.Queue()] = modict(thread=threading.current_thread())
468
- monkeypatch.setattr("codex_agent.server.current_tui", lambda: modict(pid=1234))
468
+ monkeypatch.setattr("codex_agent.server.core.current_tui", lambda: modict(pid=1234))
469
469
 
470
470
  try:
471
471
  second = client.get("/events?limit=1")
@@ -531,7 +531,7 @@ def test_agent_server_consumes_restart_wakeup(monkeypatch):
531
531
  agent = Agent(session="new", voice_enabled=False)
532
532
  service = AgentServer(InProcessAgentRuntime(agent))
533
533
  monkeypatch.setattr(
534
- "codex_agent.server.pop_restart_wakeup",
534
+ "codex_agent.server.core.pop_restart_wakeup",
535
535
  lambda: {"prompt": "continue work", "title": "resume"},
536
536
  )
537
537
  calls = []
@@ -550,15 +550,15 @@ def test_agent_server_restart_persists_wakeup_and_restarts_service(monkeypatch):
550
550
  agent = Agent(session="new", voice_enabled=False)
551
551
  service = AgentServer(InProcessAgentRuntime(agent))
552
552
  calls = []
553
- monkeypatch.setattr("codex_agent.server.write_restart_wakeup", lambda **kwargs: calls.append(("wakeup", kwargs)))
554
- monkeypatch.setattr("codex_agent.server.time.sleep", lambda delay: calls.append(("sleep", delay)))
555
- monkeypatch.setattr("codex_agent.server.restart_server_service", lambda: calls.append(("restart",)))
553
+ monkeypatch.setattr("codex_agent.server.core.write_restart_wakeup", lambda **kwargs: calls.append(("wakeup", kwargs)))
554
+ monkeypatch.setattr("codex_agent.server.core.time.sleep", lambda delay: calls.append(("sleep", delay)))
555
+ monkeypatch.setattr("codex_agent.server.core.restart_server_service", lambda: calls.append(("restart",)))
556
556
  class ImmediateThread:
557
557
  def __init__(self, target, **kwargs):
558
558
  self.target = target
559
559
  def start(self):
560
560
  self.target()
561
- monkeypatch.setattr("codex_agent.server.threading.Thread", ImmediateThread)
561
+ monkeypatch.setattr("codex_agent.server.core.threading.Thread", ImmediateThread)
562
562
  result = service.restart(prompt="resume please", title="resume", delay_seconds=0)
563
563
  assert result.restarting is True
564
564
  assert result.wakeup_persisted is True
@@ -584,15 +584,15 @@ def test_agent_server_restart_waits_for_turn_end_when_busy(monkeypatch):
584
584
  service = AgentServer(InProcessAgentRuntime(agent))
585
585
  agent.mainloop_manager.futures["running"] = modict(command="turn", status="running")
586
586
  calls = []
587
- monkeypatch.setattr("codex_agent.server.write_restart_wakeup", lambda **kwargs: calls.append(("wakeup", kwargs)))
588
- monkeypatch.setattr("codex_agent.server.time.sleep", lambda delay: calls.append(("sleep", delay)))
589
- monkeypatch.setattr("codex_agent.server.restart_server_service", lambda: calls.append(("restart",)))
587
+ monkeypatch.setattr("codex_agent.server.core.write_restart_wakeup", lambda **kwargs: calls.append(("wakeup", kwargs)))
588
+ monkeypatch.setattr("codex_agent.server.core.time.sleep", lambda delay: calls.append(("sleep", delay)))
589
+ monkeypatch.setattr("codex_agent.server.core.restart_server_service", lambda: calls.append(("restart",)))
590
590
  class ImmediateThread:
591
591
  def __init__(self, target, **kwargs):
592
592
  self.target = target
593
593
  def start(self):
594
594
  self.target()
595
- monkeypatch.setattr("codex_agent.server.threading.Thread", ImmediateThread)
595
+ monkeypatch.setattr("codex_agent.server.core.threading.Thread", ImmediateThread)
596
596
 
597
597
  result = service.restart(prompt="resume please", title="resume", delay_seconds=0)
598
598
 
@@ -1,5 +1,5 @@
1
1
  import signal
2
- from codex_agent import tui
2
+ from codex_agent.tui import lifecycle as tui
3
3
 
4
4
 
5
5
  def test_install_tui_cleanup_handlers_clears_registered_pid_on_signal(monkeypatch):