codex-agent-framework 0.1.13__tar.gz → 0.1.14__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/CHANGELOG.md +20 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/MANIFEST.in +2 -0
- {codex_agent_framework-0.1.13/codex_agent_framework.egg-info → codex_agent_framework-0.1.14}/PKG-INFO +29 -4
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/README.md +28 -3
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/__init__.py +2 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/__main__.py +53 -3
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/agent.py +31 -23
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/ai.py +22 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_commands.py +1 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_plugins/browser/__init__.py +29 -21
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_plugins/browser/controller.py +48 -8
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_plugins/desktop/__init__.py +113 -301
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_plugins/desktop/controller.py +34 -11
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_plugins/memory/__init__.py +33 -10
- codex_agent_framework-0.1.14/codex_agent/builtin_plugins/planner/__init__.py +266 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_plugins/scheduler/__init__.py +12 -4
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_tools/files.py +23 -2
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_tools/shell.py +8 -2
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/chat.py +30 -5
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/client.py +0 -2
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/config.py +18 -8
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/context.py +6 -1
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/event.py +5 -0
- codex_agent_framework-0.1.14/codex_agent/hooks.py +71 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/mainloop.py +44 -18
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/message.py +6 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/plugin.py +21 -1
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/prompts/system_prompt.txt +1 -14
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/registry.py +8 -1
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/scheduler.py +7 -11
- codex_agent_framework-0.1.14/codex_agent/scripts/install-system-dependencies.sh +149 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/sessions.py +36 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/utils.py +21 -1
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14/codex_agent_framework.egg-info}/PKG-INFO +29 -4
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent_framework.egg-info/SOURCES.txt +6 -0
- codex_agent_framework-0.1.14/dependencies.txt +88 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/pyproject.toml +2 -2
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_agent.py +314 -124
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_browser.py +33 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_chat.py +14 -8
- codex_agent_framework-0.1.14/tests/test_cli.py +139 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_client.py +2 -2
- codex_agent_framework-0.1.14/tests/test_hooks.py +188 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_local_desktop.py +62 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_memory.py +50 -3
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_messages.py +16 -0
- codex_agent_framework-0.1.14/tests/test_planner.py +133 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_utils.py +12 -2
- codex_agent_framework-0.1.13/tests/test_cli.py +0 -65
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/LICENSE +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/agent_runtime.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_plugins/__init__.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_plugins/desktop/screenshot.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_providers.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_tools/__init__.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_tools/server_tools.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_tools/system.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/builtin_tools/vision.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/command.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/get_text/__init__.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/get_text/default_gitignore +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/get_text/get_text.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/get_text/simpler_get_text.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/image.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/latex.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/memory.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/prompts/image_generation_system_prompt.txt +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/provider.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/runtime.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/server.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/service.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/status.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/stream_utils.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/tool.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/tray.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/tui.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/voice.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent/worker.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent_framework.egg-info/entry_points.txt +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent_framework.egg-info/requires.txt +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/codex_agent_framework.egg-info/top_level.txt +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/setup.cfg +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_ai.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_events.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_get_text_browser.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_image_message.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_scheduler.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_server.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_service.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_tray.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_tui.py +0 -0
- {codex_agent_framework-0.1.13 → codex_agent_framework-0.1.14}/tests/test_worker.py +0 -0
|
@@ -4,6 +4,26 @@ 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.14] - 2026-05-07
|
|
8
|
+
### Added
|
|
9
|
+
- Add a general `HookManager` with compatibility helpers and hook sites around message submission, commands, context/provider calls, runtime module loading, tool calls, shell execution, and file write/edit operations.
|
|
10
|
+
- Add planner and scheduler built-in plugins with atomic JSON persistence safeguards.
|
|
11
|
+
- Add `dependencies.txt` documenting non-Python system dependencies for browser, desktop, tray, service, TUI terminal, and audio features.
|
|
12
|
+
- Add packaged system dependency installation via `codex-agent install-system-deps` and a higher-level `codex-agent bootstrap` command that can install system dependencies then install/start the user services.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Improve TUI SSE replay/reconnect handling so replayed deltas are rendered as a compact catch-up while live deltas continue streaming normally.
|
|
16
|
+
- Keep the TUI reconnection status concise by reporting one offline message per outage and a connected message on recovery.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- Fix SSE cursor tracking so client replay state advances only from real sequenced events, avoiding missed events after reconnects.
|
|
20
|
+
- Surface asynchronous memory archive failures through `MemoryArchiveErrorEvent` instead of swallowing them silently.
|
|
21
|
+
- Clarify scheduler wakeup trigger event data around `turn_id` while preserving backward compatibility.
|
|
22
|
+
|
|
23
|
+
### Tests
|
|
24
|
+
- Expand regression coverage for hooks, planner schema/loading, scheduler persistence, TUI reconnect/replay behavior, CLI bootstrap/system dependency commands, and memory archive errors.
|
|
25
|
+
- Validate the full suite at 412 passing tests.
|
|
26
|
+
|
|
7
27
|
## [0.1.13] - 2026-05-07
|
|
8
28
|
### Changed
|
|
9
29
|
- Reorganize built-in tools into a `codex_agent.builtin_tools` package with separate modules for file, shell, vision, system, and server-tool responsibilities.
|
|
@@ -3,5 +3,7 @@ include LICENSE
|
|
|
3
3
|
include CHANGELOG.md
|
|
4
4
|
recursive-include codex_agent/prompts *.txt
|
|
5
5
|
include codex_agent/get_text/default_gitignore
|
|
6
|
+
recursive-include codex_agent/scripts *.sh
|
|
7
|
+
include dependencies.txt
|
|
6
8
|
recursive-exclude * __pycache__
|
|
7
9
|
recursive-exclude * *.py[co]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-agent-framework
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14
|
|
4
4
|
Summary: A lightweight event-driven Codex agent runtime.
|
|
5
5
|
Author: Baptiste
|
|
6
6
|
License-Expression: MIT
|
|
@@ -74,7 +74,9 @@ It provides a reusable `Agent` abstraction with persistent sessions, local tools
|
|
|
74
74
|
- Python 3.10 or newer.
|
|
75
75
|
- A working model backend configuration compatible with `codex-backend-sdk` / OpenAI usage.
|
|
76
76
|
- GTK 3 plus Ayatana AppIndicator or AppIndicator bindings are needed for the tray controller on Linux.
|
|
77
|
-
-
|
|
77
|
+
- Desktop automation on Linux/X11 uses explicit system tools: `wmctrl`, `xdotool`, and `xclip`.
|
|
78
|
+
- Playwright's managed Chromium browser is used for rendered web extraction when static fetching is insufficient.
|
|
79
|
+
- See `dependencies.txt` for the full list of optional non-Python system dependencies.
|
|
78
80
|
|
|
79
81
|
## Installation
|
|
80
82
|
|
|
@@ -90,6 +92,26 @@ For development:
|
|
|
90
92
|
python -m pip install -e '.[dev]'
|
|
91
93
|
```
|
|
92
94
|
|
|
95
|
+
Install optional Linux system dependencies for browser, desktop, tray, service, terminal, and audio features:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
codex-agent install-system-deps -- -y
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or bootstrap a local desktop setup by installing system dependencies and then installing/starting the user services:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
codex-agent bootstrap -- -y
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Useful bootstrap variants:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
codex-agent bootstrap --no-system-deps
|
|
111
|
+
codex-agent bootstrap --no-start-service -- -y
|
|
112
|
+
codex-agent install-system-deps -- --dry-run --no-tray --no-audio
|
|
113
|
+
```
|
|
114
|
+
|
|
93
115
|
## Quick start
|
|
94
116
|
|
|
95
117
|
Run the local server plus terminal TUI:
|
|
@@ -122,6 +144,8 @@ Install the user services for the server and tray controller:
|
|
|
122
144
|
codex-agent install-service
|
|
123
145
|
```
|
|
124
146
|
|
|
147
|
+
For a first-time Linux desktop setup, `codex-agent bootstrap -- -y` combines optional system dependency installation with service installation/startup. The command forwards arguments after `--` to the system dependency installer.
|
|
148
|
+
|
|
125
149
|
Or use the agent from Python:
|
|
126
150
|
|
|
127
151
|
```python
|
|
@@ -388,7 +412,7 @@ The tests isolate `AGENT_RUNTIME_DIR` automatically, so they should not create o
|
|
|
388
412
|
Current baseline:
|
|
389
413
|
|
|
390
414
|
```text
|
|
391
|
-
|
|
415
|
+
412 passed
|
|
392
416
|
```
|
|
393
417
|
|
|
394
418
|
## Packaging
|
|
@@ -400,9 +424,10 @@ python -m pip install build
|
|
|
400
424
|
python -m build
|
|
401
425
|
```
|
|
402
426
|
|
|
403
|
-
The distribution includes prompt text files
|
|
427
|
+
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`.
|
|
404
428
|
|
|
405
429
|
## Recent changes
|
|
430
|
+
- `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.
|
|
406
431
|
- `0.1.13`: reorganize built-in tools into a `codex_agent.builtin_tools` package, keeping the public import surface compatible while separating file, shell, vision, system, and server-tool modules.
|
|
407
432
|
- `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.
|
|
408
433
|
- `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.
|
|
@@ -25,7 +25,9 @@ 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
|
-
-
|
|
28
|
+
- Desktop automation on Linux/X11 uses explicit system tools: `wmctrl`, `xdotool`, and `xclip`.
|
|
29
|
+
- Playwright's managed Chromium browser is used for rendered web extraction when static fetching is insufficient.
|
|
30
|
+
- See `dependencies.txt` for the full list of optional non-Python system dependencies.
|
|
29
31
|
|
|
30
32
|
## Installation
|
|
31
33
|
|
|
@@ -41,6 +43,26 @@ For development:
|
|
|
41
43
|
python -m pip install -e '.[dev]'
|
|
42
44
|
```
|
|
43
45
|
|
|
46
|
+
Install optional Linux system dependencies for browser, desktop, tray, service, terminal, and audio features:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
codex-agent install-system-deps -- -y
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or bootstrap a local desktop setup by installing system dependencies and then installing/starting the user services:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
codex-agent bootstrap -- -y
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Useful bootstrap variants:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
codex-agent bootstrap --no-system-deps
|
|
62
|
+
codex-agent bootstrap --no-start-service -- -y
|
|
63
|
+
codex-agent install-system-deps -- --dry-run --no-tray --no-audio
|
|
64
|
+
```
|
|
65
|
+
|
|
44
66
|
## Quick start
|
|
45
67
|
|
|
46
68
|
Run the local server plus terminal TUI:
|
|
@@ -73,6 +95,8 @@ Install the user services for the server and tray controller:
|
|
|
73
95
|
codex-agent install-service
|
|
74
96
|
```
|
|
75
97
|
|
|
98
|
+
For a first-time Linux desktop setup, `codex-agent bootstrap -- -y` combines optional system dependency installation with service installation/startup. The command forwards arguments after `--` to the system dependency installer.
|
|
99
|
+
|
|
76
100
|
Or use the agent from Python:
|
|
77
101
|
|
|
78
102
|
```python
|
|
@@ -339,7 +363,7 @@ The tests isolate `AGENT_RUNTIME_DIR` automatically, so they should not create o
|
|
|
339
363
|
Current baseline:
|
|
340
364
|
|
|
341
365
|
```text
|
|
342
|
-
|
|
366
|
+
412 passed
|
|
343
367
|
```
|
|
344
368
|
|
|
345
369
|
## Packaging
|
|
@@ -351,9 +375,10 @@ python -m pip install build
|
|
|
351
375
|
python -m build
|
|
352
376
|
```
|
|
353
377
|
|
|
354
|
-
The distribution includes prompt text files
|
|
378
|
+
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`.
|
|
355
379
|
|
|
356
380
|
## Recent changes
|
|
381
|
+
- `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.
|
|
357
382
|
- `0.1.13`: reorganize built-in tools into a `codex_agent.builtin_tools` package, keeping the public import surface compatible while separating file, shell, vision, system, and server-tool modules.
|
|
358
383
|
- `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.
|
|
359
384
|
- `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.
|
|
@@ -37,6 +37,7 @@ from .message import (
|
|
|
37
37
|
AssistantMessage,
|
|
38
38
|
CompactionMessage,
|
|
39
39
|
DeveloperMessage,
|
|
40
|
+
InterruptMessage,
|
|
40
41
|
Message,
|
|
41
42
|
MessageChunk,
|
|
42
43
|
ProviderMessage,
|
|
@@ -98,6 +99,7 @@ __all__ = [
|
|
|
98
99
|
"AssistantMessage",
|
|
99
100
|
"CompactionMessage",
|
|
100
101
|
"DeveloperMessage",
|
|
102
|
+
"InterruptMessage",
|
|
101
103
|
"Message",
|
|
102
104
|
"MessageChunk",
|
|
103
105
|
"ProviderMessage",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
import subprocess
|
|
2
3
|
|
|
3
4
|
from . import Agent
|
|
4
5
|
from .chat import Chat
|
|
@@ -47,6 +48,33 @@ def run_tray_mode(base_url="http://127.0.0.1:8765"):
|
|
|
47
48
|
run_tray(base_url=base_url)
|
|
48
49
|
|
|
49
50
|
|
|
51
|
+
def run_system_deps_installer(extra_args=None):
|
|
52
|
+
script = Path(__file__).parent / "scripts" / "install-system-dependencies.sh"
|
|
53
|
+
if not script.is_file():
|
|
54
|
+
raise RuntimeError(f"System dependency installer not found: {script}")
|
|
55
|
+
return subprocess.call([str(script), *(extra_args or [])])
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def run_bootstrap(
|
|
59
|
+
host="127.0.0.1",
|
|
60
|
+
port=8765,
|
|
61
|
+
*,
|
|
62
|
+
system_deps_args=None,
|
|
63
|
+
install_system_deps=True,
|
|
64
|
+
install_service=True,
|
|
65
|
+
start_service=True,
|
|
66
|
+
):
|
|
67
|
+
if install_system_deps:
|
|
68
|
+
code = run_system_deps_installer(system_deps_args or [])
|
|
69
|
+
if code:
|
|
70
|
+
return code
|
|
71
|
+
if install_service:
|
|
72
|
+
path = install_user_service(host=host, port=port, enable=True, start=start_service)
|
|
73
|
+
action = "Installed and started" if start_service else "Installed"
|
|
74
|
+
print(f"{action} user services: {path}")
|
|
75
|
+
return 0
|
|
76
|
+
|
|
77
|
+
|
|
50
78
|
def run_local(host="127.0.0.1", port=8765):
|
|
51
79
|
import threading
|
|
52
80
|
import time
|
|
@@ -77,14 +105,19 @@ def main(argv=None):
|
|
|
77
105
|
parser.add_argument(
|
|
78
106
|
"mode",
|
|
79
107
|
nargs="?",
|
|
80
|
-
choices=["local", "server", "chat", "tray", "install-service", "uninstall-service"],
|
|
108
|
+
choices=["local", "server", "chat", "tray", "install-service", "uninstall-service", "install-system-deps", "bootstrap"],
|
|
81
109
|
default="local",
|
|
82
|
-
help="local starts an embedded server then the TUI client; server runs only the API; chat connects to an existing API; tray starts the desktop tray controller; install-service installs a user systemd service.",
|
|
110
|
+
help="local starts an embedded server then the TUI client; server runs only the API; chat connects to an existing API; tray starts the desktop tray controller; install-service installs a user systemd service; install-system-deps installs optional OS packages; bootstrap installs system deps then user services.",
|
|
83
111
|
)
|
|
84
112
|
parser.add_argument("--host", default="127.0.0.1")
|
|
85
113
|
parser.add_argument("--port", type=int, default=8765)
|
|
86
114
|
parser.add_argument("--url", default=None, help="Base URL for chat mode, e.g. http://127.0.0.1:8765")
|
|
87
|
-
|
|
115
|
+
parser.add_argument("--no-system-deps", action="store_true", help="With bootstrap, skip OS dependency installation.")
|
|
116
|
+
parser.add_argument("--no-service", action="store_true", help="With bootstrap, skip user service installation.")
|
|
117
|
+
parser.add_argument("--no-start-service", action="store_true", help="With bootstrap, install services but do not start them.")
|
|
118
|
+
args, extra_args = parser.parse_known_args(argv)
|
|
119
|
+
if args.mode not in {"install-system-deps", "bootstrap"} and extra_args:
|
|
120
|
+
parser.error(f"unrecognized arguments: {' '.join(extra_args)}")
|
|
88
121
|
|
|
89
122
|
if args.mode == "server":
|
|
90
123
|
run_server(args.host, args.port)
|
|
@@ -98,6 +131,23 @@ def main(argv=None):
|
|
|
98
131
|
elif args.mode == "uninstall-service":
|
|
99
132
|
path = uninstall_user_service(disable=True, stop=True)
|
|
100
133
|
print(f"Uninstalled user service: {path}")
|
|
134
|
+
elif args.mode == "install-system-deps":
|
|
135
|
+
installer_args = extra_args
|
|
136
|
+
if installer_args and installer_args[0] == "--":
|
|
137
|
+
installer_args = installer_args[1:]
|
|
138
|
+
raise SystemExit(run_system_deps_installer(installer_args))
|
|
139
|
+
elif args.mode == "bootstrap":
|
|
140
|
+
installer_args = extra_args
|
|
141
|
+
if installer_args and installer_args[0] == "--":
|
|
142
|
+
installer_args = installer_args[1:]
|
|
143
|
+
raise SystemExit(run_bootstrap(
|
|
144
|
+
host=args.host,
|
|
145
|
+
port=args.port,
|
|
146
|
+
system_deps_args=installer_args,
|
|
147
|
+
install_system_deps=not args.no_system_deps,
|
|
148
|
+
install_service=not args.no_service,
|
|
149
|
+
start_service=not args.no_start_service,
|
|
150
|
+
))
|
|
101
151
|
else:
|
|
102
152
|
run_local(args.host, args.port)
|
|
103
153
|
|
|
@@ -7,6 +7,7 @@ from .image import ImageMessage
|
|
|
7
7
|
from .command import CommandManager
|
|
8
8
|
from .config import ConfigManager
|
|
9
9
|
from .context import ContextManager
|
|
10
|
+
from .hooks import HookManager
|
|
10
11
|
from .mainloop import MainLoopManager
|
|
11
12
|
from .plugin import PluginsManager
|
|
12
13
|
from .registry import RegistryManager
|
|
@@ -14,7 +15,7 @@ from .runtime import RuntimeManager
|
|
|
14
15
|
from .sessions import SessionsManager
|
|
15
16
|
from .status import StatusManager
|
|
16
17
|
from .tool import ImageGenerationTool, reset_current_agent, set_current_agent
|
|
17
|
-
from .ai import AIClient
|
|
18
|
+
from .ai import AIBadRequestError, AIClient
|
|
18
19
|
from .voice import VoiceProcessor
|
|
19
20
|
from .latex import LaTeXProcessor
|
|
20
21
|
from .event import AgentInterruptRequestedEvent, AgentInterrupted, AgentInterruptedEvent, AudioPlaybackEvent, EventBus
|
|
@@ -49,7 +50,7 @@ class Agent:
|
|
|
49
50
|
self.plugins=self.plugins_manager.plugins
|
|
50
51
|
self.sessions_manager=SessionsManager(self)
|
|
51
52
|
self.runtime=RuntimeManager(self)
|
|
52
|
-
self.hooks=
|
|
53
|
+
self.hooks=HookManager(self)
|
|
53
54
|
self.on(AudioPlaybackEvent, self.handle_audio_playback)
|
|
54
55
|
self.mainloop_manager=MainLoopManager(self)
|
|
55
56
|
self._interrupt_requested=ThreadEvent()
|
|
@@ -126,7 +127,7 @@ class Agent:
|
|
|
126
127
|
self.emit(AgentInterruptedEvent, reason=reason)
|
|
127
128
|
self.emit(AgentInterruptRequestedEvent, reason=reason, interrupted=True, error=str(abort_error) if abort_error else "")
|
|
128
129
|
return True
|
|
129
|
-
|
|
130
|
+
|
|
130
131
|
def clear_interrupt(self):
|
|
131
132
|
self._interrupt_requested.clear()
|
|
132
133
|
|
|
@@ -189,7 +190,6 @@ class Agent:
|
|
|
189
190
|
if self.mainloop_manager.dumping_pending:
|
|
190
191
|
return self.sessions_manager.append_message(msg)
|
|
191
192
|
return self.mainloop_manager.add_pending_message(msg)
|
|
192
|
-
|
|
193
193
|
|
|
194
194
|
def new_message(self,pending=False,**kwargs):
|
|
195
195
|
"""Helper to create and add a message based on provided keyword arguments.
|
|
@@ -203,7 +203,7 @@ class Agent:
|
|
|
203
203
|
"""
|
|
204
204
|
msg=message_from_data(kwargs)
|
|
205
205
|
return self.add_message(msg,pending=pending)
|
|
206
|
-
|
|
206
|
+
|
|
207
207
|
def add_image(self,source,pending=False,**kwargs):
|
|
208
208
|
if source:
|
|
209
209
|
img=ImageMessage(
|
|
@@ -247,21 +247,24 @@ class Agent:
|
|
|
247
247
|
if hasattr(self, "mainloop_manager") and self.mainloop_manager.should_queue_state_command():
|
|
248
248
|
return self.submit("add_hook", key=key, func=func).wait()
|
|
249
249
|
|
|
250
|
-
self.hooks.
|
|
251
|
-
return func
|
|
250
|
+
return self.hooks.add(key, func)
|
|
252
251
|
|
|
253
252
|
def has_hook(self, key):
|
|
254
|
-
return
|
|
253
|
+
return self.hooks.has(key)
|
|
254
|
+
|
|
255
|
+
def run_hook(self, key, **payload):
|
|
256
|
+
agent_context = set_current_agent(self)
|
|
257
|
+
try:
|
|
258
|
+
return self.hooks.run(key, **payload)
|
|
259
|
+
finally:
|
|
260
|
+
reset_current_agent(agent_context)
|
|
255
261
|
|
|
256
262
|
def run_hooks(self, key, *args):
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
finally:
|
|
263
|
-
reset_current_agent(agent_context)
|
|
264
|
-
return results
|
|
263
|
+
agent_context = set_current_agent(self)
|
|
264
|
+
try:
|
|
265
|
+
return self.hooks.run_legacy(key, *args)
|
|
266
|
+
finally:
|
|
267
|
+
reset_current_agent(agent_context)
|
|
265
268
|
|
|
266
269
|
def get_messages(self,filter=None):
|
|
267
270
|
return self.sessions_manager.messages(filter=filter)
|
|
@@ -321,16 +324,21 @@ class Agent:
|
|
|
321
324
|
)
|
|
322
325
|
messages = self.context_manager.get_context()
|
|
323
326
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
327
|
+
try:
|
|
328
|
+
message=self.ai.run(
|
|
329
|
+
messages=messages,
|
|
330
|
+
tools=self.registry.get_response_tools(),
|
|
331
|
+
prompt_cache_key=self.current_session_id,
|
|
332
|
+
**self.config.extract('model','reasoning_effort','verbosity','parallel_tool_calls')
|
|
333
|
+
)
|
|
334
|
+
except AIBadRequestError:
|
|
335
|
+
self.sessions_manager.restore_api_success_backup()
|
|
336
|
+
raise
|
|
330
337
|
self.sessions_manager.append_message(message)
|
|
331
338
|
for item in message.output_items:
|
|
332
339
|
if self.registry.server_tool_for_item(item):
|
|
333
340
|
self.sessions_manager.append_message(ServerToolResponseMessage(item=item))
|
|
341
|
+
self.sessions_manager.save_api_success_backup()
|
|
334
342
|
return message
|
|
335
343
|
|
|
336
344
|
def speak(self,text,**kwargs):
|
|
@@ -381,7 +389,7 @@ class Agent:
|
|
|
381
389
|
"""
|
|
382
390
|
if source is None:
|
|
383
391
|
raise ValueError("An audio source must be provided")
|
|
384
|
-
|
|
392
|
+
|
|
385
393
|
# Transcribe audio to text
|
|
386
394
|
transcribed_text = self.ai.audio_to_text(
|
|
387
395
|
source=source,
|
|
@@ -51,6 +51,12 @@ class AIClientError(Exception):
|
|
|
51
51
|
"""Base exception for AI client errors."""
|
|
52
52
|
pass
|
|
53
53
|
|
|
54
|
+
|
|
55
|
+
class AIBadRequestError(AIClientError):
|
|
56
|
+
"""Raised when the backend rejects the request as malformed."""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
54
60
|
class APIAuthenticationError(AIClientError):
|
|
55
61
|
"""Exception raised for OpenAI API authentication failures."""
|
|
56
62
|
pass
|
|
@@ -237,6 +243,8 @@ class AIClient:
|
|
|
237
243
|
**stream_params
|
|
238
244
|
)
|
|
239
245
|
except Exception as e:
|
|
246
|
+
if self.is_bad_request_error(e):
|
|
247
|
+
raise AIBadRequestError(str(e)) from e
|
|
240
248
|
import traceback
|
|
241
249
|
import sys
|
|
242
250
|
print("\n[ERROR in ai.run setup]:", file=sys.stderr)
|
|
@@ -262,8 +270,20 @@ class AIClient:
|
|
|
262
270
|
except ResponseAborted:
|
|
263
271
|
raise AgentInterrupted()
|
|
264
272
|
except Exception as e:
|
|
273
|
+
if self.is_bad_request_error(e):
|
|
274
|
+
raise AIBadRequestError(str(e)) from e
|
|
265
275
|
yield MessageChunk(content=f"Hmmm, sorry! There was an error while reading the Codex backend stream. Here is the error message I got:\n\n ```\n{str(e)}\n```")
|
|
266
276
|
|
|
277
|
+
def is_bad_request_error(self, exc):
|
|
278
|
+
response = getattr(exc, "response", None)
|
|
279
|
+
status_code = getattr(response, "status_code", None)
|
|
280
|
+
if status_code == 400:
|
|
281
|
+
return True
|
|
282
|
+
status_code = getattr(exc, "status_code", None)
|
|
283
|
+
if status_code == 400:
|
|
284
|
+
return True
|
|
285
|
+
return "400" in str(exc) and "Bad Request" in str(exc)
|
|
286
|
+
|
|
267
287
|
def get_quota_usage(self):
|
|
268
288
|
try:
|
|
269
289
|
return self.codex_client.usage()
|
|
@@ -346,6 +366,8 @@ class AIClient:
|
|
|
346
366
|
return MessageChunk(tool_calls=[tool_call])
|
|
347
367
|
return MessageChunk(output_items=[item])
|
|
348
368
|
if isinstance(event, ResponseFailed):
|
|
369
|
+
if getattr(event, "code", None) == 400 or "bad_request" in str(getattr(event, "code", "")).lower():
|
|
370
|
+
raise AIBadRequestError(str(getattr(event, "message", "") or event))
|
|
349
371
|
return MessageChunk(content=f"Hmmm, sorry! The Codex backend returned an error:\n\n```\n[{event.code}] {event.message}\n```")
|
|
350
372
|
return None
|
|
351
373
|
|
|
@@ -30,19 +30,27 @@ def _format_browser_status(status: dict) -> str:
|
|
|
30
30
|
class BrowserPlugin(Plugin):
|
|
31
31
|
name = "browser"
|
|
32
32
|
description = "Persistent Chromium browser tools and active-tab context provider."
|
|
33
|
+
system_prompt = """
|
|
34
|
+
# Browser
|
|
35
|
+
|
|
36
|
+
Use browser tools for interactive web pages, logged-in sessions, and workflows that require navigation, clicks, forms, or keyboard input in Chromium.
|
|
37
|
+
When the browser is open, the browser_state provider gives the current active tab snapshot and screenshot; treat that provider context as the source of truth.
|
|
38
|
+
Use the element ids from the latest browser_state snapshot with browser_click, browser_fill, browser_select, and browser_press.
|
|
39
|
+
Prefer browser_goto to navigate the current tab, browser_open when you need to start the browser or open a new tab, and browser_select_tab when the user explicitly wants another tab.
|
|
40
|
+
"""
|
|
33
41
|
|
|
34
42
|
@property
|
|
35
43
|
def controller(self):
|
|
36
44
|
return get_browser_controller()
|
|
37
45
|
|
|
38
|
-
def
|
|
46
|
+
def tool_result(self, call):
|
|
39
47
|
try:
|
|
40
48
|
return call()
|
|
41
49
|
except BrowserError as exc:
|
|
42
50
|
return f"Browser error: {exc}"
|
|
43
51
|
|
|
44
52
|
@tool
|
|
45
|
-
def
|
|
53
|
+
def open(self, url: str = None, headless: bool = False, profile_name: str = "default", new_tab: bool = None):
|
|
46
54
|
"""
|
|
47
55
|
description: |
|
|
48
56
|
Open the persistent Playwright/Chromium browser. If the browser is closed, this starts it.
|
|
@@ -61,17 +69,17 @@ class BrowserPlugin(Plugin):
|
|
|
61
69
|
description: Force opening a new tab. Defaults to true when browser is already open, false for a fresh session.
|
|
62
70
|
type: boolean
|
|
63
71
|
"""
|
|
64
|
-
return self.
|
|
72
|
+
return self.tool_result(lambda: _format_browser_status(self.controller.open(url=url, headless=headless, profile_name=profile_name, new_tab=new_tab)))
|
|
65
73
|
|
|
66
74
|
@tool
|
|
67
|
-
def
|
|
75
|
+
def close(self):
|
|
68
76
|
"""
|
|
69
77
|
description: Close the persistent Playwright/Chromium browser session.
|
|
70
78
|
"""
|
|
71
|
-
return self.
|
|
79
|
+
return self.tool_result(lambda: self.controller.close())
|
|
72
80
|
|
|
73
81
|
@tool
|
|
74
|
-
def
|
|
82
|
+
def select_tab(self, index: int):
|
|
75
83
|
"""
|
|
76
84
|
description: Select an open browser tab by zero-based index.
|
|
77
85
|
parameters:
|
|
@@ -81,17 +89,17 @@ class BrowserPlugin(Plugin):
|
|
|
81
89
|
required:
|
|
82
90
|
- index
|
|
83
91
|
"""
|
|
84
|
-
return self.
|
|
92
|
+
return self.tool_result(lambda: _format_browser_status(self.controller.select_tab(index)))
|
|
85
93
|
|
|
86
94
|
@tool
|
|
87
|
-
def
|
|
95
|
+
def previous_page(self):
|
|
88
96
|
"""
|
|
89
97
|
description: Navigate the active browser tab back in history.
|
|
90
98
|
"""
|
|
91
|
-
return self.
|
|
99
|
+
return self.tool_result(lambda: self.controller.previous_page())
|
|
92
100
|
|
|
93
101
|
@tool
|
|
94
|
-
def
|
|
102
|
+
def goto(self, url: str):
|
|
95
103
|
"""
|
|
96
104
|
description: Navigate the active browser tab to a URL, without opening or switching tabs.
|
|
97
105
|
parameters:
|
|
@@ -101,9 +109,9 @@ class BrowserPlugin(Plugin):
|
|
|
101
109
|
required:
|
|
102
110
|
- url
|
|
103
111
|
"""
|
|
104
|
-
return self.
|
|
112
|
+
return self.tool_result(lambda: self.controller.goto(url))
|
|
105
113
|
|
|
106
|
-
def
|
|
114
|
+
def snapshot(self, max_elements: int = 120, include_json: bool = False):
|
|
107
115
|
"""
|
|
108
116
|
description: |
|
|
109
117
|
Return a compact, action-oriented snapshot of the active web browser page.
|
|
@@ -116,10 +124,10 @@ class BrowserPlugin(Plugin):
|
|
|
116
124
|
description: Include structured JSON snapshot data after the readable snapshot.
|
|
117
125
|
type: boolean
|
|
118
126
|
"""
|
|
119
|
-
return self.
|
|
127
|
+
return self.tool_result(lambda: self.controller.snapshot(max_elements=max_elements, include_json=include_json))
|
|
120
128
|
|
|
121
129
|
@tool
|
|
122
|
-
def
|
|
130
|
+
def click(self, element_id: str):
|
|
123
131
|
"""
|
|
124
132
|
description: Click an element from the latest browser provider snapshot by ID.
|
|
125
133
|
parameters:
|
|
@@ -129,10 +137,10 @@ class BrowserPlugin(Plugin):
|
|
|
129
137
|
required:
|
|
130
138
|
- element_id
|
|
131
139
|
"""
|
|
132
|
-
return self.
|
|
140
|
+
return self.tool_result(lambda: self.controller.click(element_id))
|
|
133
141
|
|
|
134
142
|
@tool
|
|
135
|
-
def
|
|
143
|
+
def fill(self, element_id: str, text: str):
|
|
136
144
|
"""
|
|
137
145
|
description: Fill an input/textarea/contenteditable element from the latest browser provider snapshot by ID.
|
|
138
146
|
parameters:
|
|
@@ -146,10 +154,10 @@ class BrowserPlugin(Plugin):
|
|
|
146
154
|
- element_id
|
|
147
155
|
- text
|
|
148
156
|
"""
|
|
149
|
-
return self.
|
|
157
|
+
return self.tool_result(lambda: self.controller.fill(element_id, text))
|
|
150
158
|
|
|
151
159
|
@tool
|
|
152
|
-
def
|
|
160
|
+
def select(self, element_id: str, value):
|
|
153
161
|
"""
|
|
154
162
|
description: Select an option value on a select element from the latest browser provider snapshot by ID.
|
|
155
163
|
parameters:
|
|
@@ -162,10 +170,10 @@ class BrowserPlugin(Plugin):
|
|
|
162
170
|
- element_id
|
|
163
171
|
- value
|
|
164
172
|
"""
|
|
165
|
-
return self.
|
|
173
|
+
return self.tool_result(lambda: self.controller.select(element_id, value))
|
|
166
174
|
|
|
167
175
|
@tool
|
|
168
|
-
def
|
|
176
|
+
def press(self, key: str, element_id: str = None):
|
|
169
177
|
"""
|
|
170
178
|
description: Press a keyboard key globally or on a browser provider snapshot element ID.
|
|
171
179
|
parameters:
|
|
@@ -178,7 +186,7 @@ class BrowserPlugin(Plugin):
|
|
|
178
186
|
required:
|
|
179
187
|
- key
|
|
180
188
|
"""
|
|
181
|
-
return self.
|
|
189
|
+
return self.tool_result(lambda: self.controller.press(key, element_id=element_id))
|
|
182
190
|
|
|
183
191
|
@provider
|
|
184
192
|
def browser_state(self):
|