codex-agent-framework 0.1.10__tar.gz → 0.1.12__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 (67) hide show
  1. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/CHANGELOG.md +31 -0
  2. {codex_agent_framework-0.1.10/codex_agent_framework.egg-info → codex_agent_framework-0.1.12}/PKG-INFO +5 -4
  3. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/README.md +3 -1
  4. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/agent.py +81 -11
  5. codex_agent_framework-0.1.12/codex_agent/browser.py +651 -0
  6. codex_agent_framework-0.1.12/codex_agent/builtin_providers.py +74 -0
  7. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/builtin_tools.py +248 -17
  8. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/chat.py +19 -1
  9. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/get_text/get_text.py +22 -44
  10. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/prompts/system_prompt.txt +8 -5
  11. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/utils.py +127 -33
  12. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12/codex_agent_framework.egg-info}/PKG-INFO +5 -4
  13. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent_framework.egg-info/SOURCES.txt +3 -1
  14. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent_framework.egg-info/requires.txt +1 -2
  15. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/pyproject.toml +2 -3
  16. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_agent.py +94 -2
  17. codex_agent_framework-0.1.12/tests/test_browser.py +235 -0
  18. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_chat.py +66 -0
  19. codex_agent_framework-0.1.12/tests/test_get_text_browser.py +55 -0
  20. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_utils.py +109 -0
  21. codex_agent_framework-0.1.10/codex_agent/builtin_providers.py +0 -42
  22. codex_agent_framework-0.1.10/codex_agent/get_webdriver.py +0 -44
  23. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/LICENSE +0 -0
  24. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/MANIFEST.in +0 -0
  25. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/__init__.py +0 -0
  26. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/__main__.py +0 -0
  27. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/agent_runtime.py +0 -0
  28. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/ai.py +0 -0
  29. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/builtin_commands.py +0 -0
  30. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/client.py +0 -0
  31. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/command.py +0 -0
  32. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/event.py +0 -0
  33. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/get_text/__init__.py +0 -0
  34. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/get_text/default_gitignore +0 -0
  35. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/get_text/simpler_get_text.py +0 -0
  36. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/image.py +0 -0
  37. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/latex.py +0 -0
  38. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/memory.py +0 -0
  39. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/message.py +0 -0
  40. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/prompts/image_generation_system_prompt.txt +0 -0
  41. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/provider.py +0 -0
  42. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/scheduler.py +0 -0
  43. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/server.py +0 -0
  44. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/service.py +0 -0
  45. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/stream_utils.py +0 -0
  46. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/tool.py +0 -0
  47. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/tray.py +0 -0
  48. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/tui.py +0 -0
  49. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/voice.py +0 -0
  50. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent/worker.py +0 -0
  51. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
  52. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent_framework.egg-info/entry_points.txt +0 -0
  53. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/codex_agent_framework.egg-info/top_level.txt +0 -0
  54. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/setup.cfg +0 -0
  55. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_ai.py +0 -0
  56. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_cli.py +0 -0
  57. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_client.py +0 -0
  58. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_events.py +0 -0
  59. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_image_message.py +0 -0
  60. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_memory.py +0 -0
  61. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_messages.py +0 -0
  62. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_scheduler.py +0 -0
  63. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_server.py +0 -0
  64. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_service.py +0 -0
  65. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_tray.py +0 -0
  66. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_tui.py +0 -0
  67. {codex_agent_framework-0.1.10 → codex_agent_framework-0.1.12}/tests/test_worker.py +0 -0
@@ -3,6 +3,37 @@
3
3
  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
+
7
+ ## [0.1.12] - 2026-05-05
8
+ ### Added
9
+ - Add a persistent Playwright/Chromium browser controller with tools to open/close the browser, select tabs, navigate, click, fill, select options, press keys, inspect action snapshots, and capture screenshots.
10
+ - Add `browser_goto(url)` to navigate the active tab without opening or switching tabs.
11
+ - Add screenshot-backed visual fallback for pages where DOM snapshots are insufficient, including PDF viewers and image-heavy pages.
12
+
13
+ ### Changed
14
+ - Replace the Selenium/Gecko rendered extraction path with Playwright-managed Chromium.
15
+ - Update web extraction dependencies and documentation to install Chromium with Playwright.
16
+
17
+ ### Tests
18
+ - Add browser controller and rendered extraction regression coverage.
19
+
20
+ ## [0.1.11] - 2026-05-05
21
+ ### Added
22
+ - Add a separate `view` tool for folders, URLs, rich document extraction, and Python object inspection.
23
+ - Add context status counts for persistent and temporary session messages, surfaced in the TUI status bar.
24
+
25
+ ### Changed
26
+ - Make `read` a strict local UTF-8 file reader with reliable forced line numbers and regex pattern search by default.
27
+ - Keep extracted `view` output unnumbered, including search snippets, because transformed document line structure is not source-of-truth reliable.
28
+ - Update the system prompt to direct agents to use `read` before targeted edits and `view` for broad extraction.
29
+
30
+ ### Fixed
31
+ - Preserve blank lines and real source line numbers in `read` pattern snippets.
32
+ - Apply line-based pagination before token truncation.
33
+
34
+ ### Tests
35
+ - Add regression coverage for strict `read`, unnumbered `view`, blank-line preservation, regex/literal search, and persistent/temporary message status counts.
36
+
6
37
  ## [0.1.10] - 2026-05-05
7
38
  ### Fixed
8
39
  - Persist only backend `compaction_summary` items in compaction messages, avoiding bulky compacted conversation payloads while preserving summary replay.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-agent-framework
3
- Version: 0.1.10
3
+ Version: 0.1.12
4
4
  Summary: A lightweight event-driven Codex agent runtime.
5
5
  Author: Baptiste
6
6
  License-Expression: MIT
@@ -20,7 +20,6 @@ Requires-Dist: beautifulsoup4
20
20
  Requires-Dist: codex-backend-sdk
21
21
  Requires-Dist: fastapi
22
22
  Requires-Dist: filetype
23
- Requires-Dist: get-gecko-driver
24
23
  Requires-Dist: modict
25
24
  Requires-Dist: numpy
26
25
  Requires-Dist: odfpy
@@ -28,6 +27,7 @@ Requires-Dist: openai
28
27
  Requires-Dist: openpyxl
29
28
  Requires-Dist: pathspec
30
29
  Requires-Dist: pillow
30
+ Requires-Dist: playwright
31
31
  Requires-Dist: pydub
32
32
  Requires-Dist: pypdf
33
33
  Requires-Dist: pynteract
@@ -36,7 +36,6 @@ Requires-Dist: PyYAML
36
36
  Requires-Dist: regex
37
37
  Requires-Dist: requests
38
38
  Requires-Dist: rich
39
- Requires-Dist: selenium
40
39
  Requires-Dist: textual
41
40
  Requires-Dist: tiktoken
42
41
  Requires-Dist: trafilatura
@@ -73,7 +72,7 @@ It provides a reusable `Agent` abstraction with persistent sessions, local tools
73
72
  - Python 3.10 or newer.
74
73
  - A working model backend configuration compatible with `codex-backend-sdk` / OpenAI usage.
75
74
  - GTK 3 plus Ayatana AppIndicator or AppIndicator bindings are needed for the tray controller on Linux.
76
- - Firefox / GeckoDriver may be needed for Selenium-backed web extraction paths.
75
+ - Playwright's managed Chromium browser is used for rendered web extraction when static fetching is insufficient. Install it with `python -m playwright install chromium` if needed.
77
76
 
78
77
  ## Installation
79
78
 
@@ -400,6 +399,8 @@ python -m build
400
399
  The distribution includes prompt text files and `codex_agent/get_text/default_gitignore` through package data and `MANIFEST.in`.
401
400
 
402
401
  ## Recent changes
402
+ - `0.1.12`: add a persistent Playwright/Chromium browser controller with tab navigation, DOM/action snapshots, screenshots, form/click/key tools, and `browser_goto(url)` for active-tab navigation.
403
+ - `0.1.11`: split strict line-numbered UTF-8 `read` from unnumbered extracted `view`, preserve blank lines in read snippets, and show persistent+temporary message counts in the TUI status bar.
403
404
  - `0.1.10`: persist only backend compaction summaries, drop bulky compacted conversation payloads, and refresh context status after compaction.
404
405
  - `0.1.9`: maintenance packaging release after validating the local execution environment and deploy workflow.
405
406
  - `0.1.8`: scope TUI replay/SSE catch-up to the active session and make bash/python subprocesses inherit the project Python environment, including service-launched agents.
@@ -25,7 +25,7 @@ It provides a reusable `Agent` abstraction with persistent sessions, local tools
25
25
  - Python 3.10 or newer.
26
26
  - A working model backend configuration compatible with `codex-backend-sdk` / OpenAI usage.
27
27
  - GTK 3 plus Ayatana AppIndicator or AppIndicator bindings are needed for the tray controller on Linux.
28
- - Firefox / GeckoDriver may be needed for Selenium-backed web extraction paths.
28
+ - Playwright's managed Chromium browser is used for rendered web extraction when static fetching is insufficient. Install it with `python -m playwright install chromium` if needed.
29
29
 
30
30
  ## Installation
31
31
 
@@ -352,6 +352,8 @@ python -m build
352
352
  The distribution includes prompt text files and `codex_agent/get_text/default_gitignore` through package data and `MANIFEST.in`.
353
353
 
354
354
  ## Recent changes
355
+ - `0.1.12`: add a persistent Playwright/Chromium browser controller with tab navigation, DOM/action snapshots, screenshots, form/click/key tools, and `browser_goto(url)` for active-tab navigation.
356
+ - `0.1.11`: split strict line-numbered UTF-8 `read` from unnumbered extracted `view`, preserve blank lines in read snippets, and show persistent+temporary message counts in the TUI status bar.
355
357
  - `0.1.10`: persist only backend compaction summaries, drop bulky compacted conversation payloads, and refresh context status after compaction.
356
358
  - `0.1.9`: maintenance packaging release after validating the local execution environment and deploy workflow.
357
359
  - `0.1.8`: scope TUI replay/SSE catch-up to the active session and make bash/python subprocesses inherit the project Python environment, including service-launched agents.
@@ -952,9 +952,10 @@ class Agent:
952
952
  def add_provider(self, func=None, name=None):
953
953
  """Register an ephemeral context provider.
954
954
 
955
- Providers are called each time context is built. They must return a
956
- string or None. Returned strings are wrapped in ProviderMessage and are
957
- not persisted to the session.
955
+ Providers are called each time context is built. They may return None,
956
+ a string, a Message instance, or a list/tuple mixing strings and Message
957
+ instances. Strings are wrapped in ProviderMessage; Message instances are
958
+ passed through as already formatted ephemeral context.
958
959
  """
959
960
  if func is None:
960
961
  def decorator(f):
@@ -1090,11 +1091,52 @@ class Agent:
1090
1091
  return [msg for msg in self.session.messages if filter(msg)]
1091
1092
  return sort(self.session.messages)
1092
1093
 
1094
+ def provider_context_wrapper(self, provider_name, content):
1095
+ return f'<context_provider name="{provider_name}">\n{content}\n</context_provider>'
1096
+
1097
+ def provider_message_from_output(self, provider_name, output):
1098
+ if isinstance(output, str):
1099
+ return ProviderMessage(name=provider_name, content=output)
1100
+ if not isinstance(output, Message):
1101
+ raise TypeError(
1102
+ f"Provider {provider_name!r} items must be str or Message, got {type(output).__name__}."
1103
+ )
1104
+
1105
+ message = output.copy()
1106
+ if isinstance(message, ProviderMessage):
1107
+ if not message.get("name"):
1108
+ message.name = provider_name
1109
+ return message
1110
+
1111
+ if isinstance(message, ImageMessage):
1112
+ description = message.get("description") or "Image provided by an ephemeral context provider."
1113
+ message.description = self.provider_context_wrapper(provider_name, description)
1114
+ return message
1115
+
1116
+ content = message.get("content")
1117
+ if isinstance(content, str):
1118
+ message.content = self.provider_context_wrapper(provider_name, content)
1119
+ elif isinstance(content, list):
1120
+ wrapped_parts = []
1121
+ for part in content:
1122
+ if isinstance(part, str):
1123
+ wrapped_parts.append(self.provider_context_wrapper(provider_name, part))
1124
+ elif isinstance(part, dict) and part.get("type") == "text":
1125
+ part = dict(part)
1126
+ part["text"] = self.provider_context_wrapper(provider_name, part.get("text", ""))
1127
+ wrapped_parts.append(part)
1128
+ else:
1129
+ wrapped_parts.append(part)
1130
+ message.content = wrapped_parts
1131
+ return message
1132
+
1093
1133
  def get_providers_messages(self):
1094
- """Retrieves messages from all registered providers.
1134
+ """Retrieves ephemeral messages from all registered providers.
1095
1135
 
1096
- Returns:
1097
- List of ProviderMessage objects generated by providers.
1136
+ Providers may return None, a string, a Message, or a list/tuple of
1137
+ strings and Messages. Each output item becomes its own message; strings
1138
+ are wrapped in ProviderMessage, while existing Messages keep their type
1139
+ and only their textual content is marked as provider context.
1098
1140
  """
1099
1141
  provider_msgs=[]
1100
1142
  for provider_name, provider in self.providers.items():
@@ -1105,11 +1147,11 @@ class Agent:
1105
1147
  reset_current_agent(agent_context)
1106
1148
  if provider_output is None:
1107
1149
  continue
1108
- if not isinstance(provider_output, str):
1109
- raise TypeError(
1110
- f"Provider {provider_name!r} must return str or None, got {type(provider_output).__name__}."
1111
- )
1112
- provider_msgs.append(ProviderMessage(name=provider_name, content=provider_output))
1150
+ outputs = provider_output if isinstance(provider_output, (list, tuple)) else [provider_output]
1151
+ for output in outputs:
1152
+ if output is None:
1153
+ continue
1154
+ provider_msgs.append(self.provider_message_from_output(provider_name, output))
1113
1155
  return provider_msgs
1114
1156
 
1115
1157
  def context_status(self):
@@ -1154,6 +1196,14 @@ class Agent:
1154
1196
  visible_history_count = len(truncated_others[max(0, history_start):])
1155
1197
  sent_session_messages = visible_history_count + len(images)
1156
1198
  total_session_messages = len(self.session.messages) if self.session else 0
1199
+ persistent_session_messages = sum(
1200
+ 1 for msg in session_messages
1201
+ if msg.get("turns_left", -1) == -1
1202
+ )
1203
+ temporary_session_messages = sum(
1204
+ 1 for msg in session_messages
1205
+ if isinstance(msg.get("turns_left", -1), int) and msg.get("turns_left", -1) > 0
1206
+ )
1157
1207
  percent = (current_count / input_limit * 100) if input_limit else 0
1158
1208
  status = modict(
1159
1209
  used_tokens=current_count,
@@ -1167,6 +1217,8 @@ class Agent:
1167
1217
  sent_session_messages=sent_session_messages,
1168
1218
  total_session_messages=total_session_messages,
1169
1219
  sent_session_message_percent=(sent_session_messages / total_session_messages * 100) if total_session_messages else 0,
1220
+ persistent_session_messages=persistent_session_messages,
1221
+ temporary_session_messages=temporary_session_messages,
1170
1222
  context_session_messages=len(session_messages),
1171
1223
  image_messages=len(images),
1172
1224
  pending_messages=len(self.pending),
@@ -1199,6 +1251,8 @@ class Agent:
1199
1251
  sent_session_messages=estimated.sent_session_messages,
1200
1252
  total_session_messages=estimated.total_session_messages,
1201
1253
  sent_session_message_percent=estimated.sent_session_message_percent,
1254
+ persistent_session_messages=estimated.get("persistent_session_messages", 0),
1255
+ temporary_session_messages=estimated.get("temporary_session_messages", 0),
1202
1256
  quota_5h_percent=None,
1203
1257
  quota_7d_percent=None,
1204
1258
  model=self.config.get("model") or "gpt-5.4",
@@ -1223,6 +1277,8 @@ class Agent:
1223
1277
  sent_session_messages=estimated.sent_session_messages,
1224
1278
  total_session_messages=estimated.total_session_messages,
1225
1279
  sent_session_message_percent=estimated.sent_session_message_percent,
1280
+ persistent_session_messages=estimated.get("persistent_session_messages", 0),
1281
+ temporary_session_messages=estimated.get("temporary_session_messages", 0),
1226
1282
  model=self.config.get("model") or "gpt-5.4",
1227
1283
  busy=self.busy,
1228
1284
  current_turn_id=self.current_turn_id,
@@ -1244,6 +1300,8 @@ class Agent:
1244
1300
  sent_session_messages=estimated.sent_session_messages,
1245
1301
  total_session_messages=estimated.total_session_messages,
1246
1302
  sent_session_message_percent=estimated.sent_session_message_percent,
1303
+ persistent_session_messages=estimated.get("persistent_session_messages", 0),
1304
+ temporary_session_messages=estimated.get("temporary_session_messages", 0),
1247
1305
  model=self.config.get("model") or "gpt-5.4",
1248
1306
  busy=self.busy,
1249
1307
  current_turn_id=self.current_turn_id,
@@ -1267,6 +1325,8 @@ class Agent:
1267
1325
  sent_session_messages=context_status.get("sent_session_messages", 0),
1268
1326
  total_session_messages=context_status.get("total_session_messages", 0),
1269
1327
  sent_session_message_percent=context_status.get("sent_session_message_percent", 0),
1328
+ persistent_session_messages=context_status.get("persistent_session_messages", 0),
1329
+ temporary_session_messages=context_status.get("temporary_session_messages", 0),
1270
1330
  quota_5h_percent=wham.quota_5h_percent,
1271
1331
  quota_7d_percent=wham.quota_7d_percent,
1272
1332
  model=model or self.config.get("model") or "gpt-5.4",
@@ -1383,6 +1443,14 @@ class Agent:
1383
1443
  formatted_context = list(msg.format(context=self.shell.namespace) for msg in context)
1384
1444
  total_session_messages = len(self.session.messages) if self.session else 0
1385
1445
  sent_session_messages = len(history) + len(images)
1446
+ persistent_session_messages = sum(
1447
+ 1 for msg in session_messages
1448
+ if msg.get("turns_left", -1) == -1
1449
+ )
1450
+ temporary_session_messages = sum(
1451
+ 1 for msg in session_messages
1452
+ if isinstance(msg.get("turns_left", -1), int) and msg.get("turns_left", -1) > 0
1453
+ )
1386
1454
  token_count_source = "local_estimate"
1387
1455
  try:
1388
1456
  current_count = self.ai.count_input_tokens(
@@ -1398,6 +1466,8 @@ class Agent:
1398
1466
  sent_session_messages=sent_session_messages,
1399
1467
  total_session_messages=total_session_messages,
1400
1468
  sent_session_message_percent=(sent_session_messages / total_session_messages * 100) if total_session_messages else 0,
1469
+ persistent_session_messages=persistent_session_messages,
1470
+ temporary_session_messages=temporary_session_messages,
1401
1471
  token_count_source=token_count_source,
1402
1472
  )
1403
1473
  return formatted_context