codex-agent-framework 0.1.26__tar.gz → 0.1.28__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.26 → codex_agent_framework-0.1.28}/CHANGELOG.md +17 -0
- {codex_agent_framework-0.1.26/codex_agent_framework.egg-info → codex_agent_framework-0.1.28}/PKG-INFO +2 -1
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/README.md +1 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/__init__.py +2 -1
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/agent.py +39 -29
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/agent_runtime.py +152 -5
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/context/__init__.py +12 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/files/__init__.py +3 -3
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/files/tools.py +14 -6
- codex_agent_framework-0.1.28/codex_agent/builtin_plugins/interface/__init__.py +53 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/context.py +43 -3
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/event.py +4 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/server/core.py +5 -1
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/status.py +124 -29
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/stream.py +40 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/tui/chat.py +293 -21
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/utils.py +151 -19
- codex_agent_framework-0.1.28/codex_agent/version.py +1 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28/codex_agent_framework.egg-info}/PKG-INFO +2 -1
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/pyproject.toml +1 -1
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_agent.py +56 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_chat.py +246 -25
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_server.py +169 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_utils.py +64 -3
- codex_agent_framework-0.1.26/codex_agent/builtin_plugins/interface/__init__.py +0 -38
- codex_agent_framework-0.1.26/codex_agent/version.py +0 -1
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/LICENSE +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/MANIFEST.in +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/__main__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/ai.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/browser.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_commands.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/bash/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/bash/requirements.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/browser/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/browser/requirements.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/content/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/content/requirements.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/content/system.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/content/vision.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/desktop/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/desktop/requirements.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/environment/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/environment/requirements.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/files/requirements.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/image_generation/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/image_generation/image_generation_system_prompt.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/image_generation/requirements.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/interface/requirements.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/memory/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/memory/requirements.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/planner/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/planner/requirements.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/python/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/python/requirements.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/realtime/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/realtime/audio.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/realtime/bridge.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/realtime/manager.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/realtime/requirements.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/realtime/session.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/scheduler/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/scheduler/requirements.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/subagents/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/subagents/profiles/explorer.json +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/subagents/requirements.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/tts/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/builtin_plugins/tts/requirements.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/cli/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/cli/headless.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/cli/main.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/cli/output.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/cli/root.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/cli/runner.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/client.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/command.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/config.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/desktop.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/get_text/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/get_text/default_gitignore +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/get_text/get_text.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/get_text/simpler_get_text.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/hooks.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/image.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/latex.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/mainloop.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/message.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/plugin.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/prompts/system_prompt.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/provider.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/runtime.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/scheduler.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/scripts/install-system-dependencies.sh +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/server/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/server/app.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/service.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/sessions.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/stream_utils.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/tool.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/tray.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/tui/__init__.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/tui/lifecycle.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent/worker.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent_framework.egg-info/SOURCES.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent_framework.egg-info/entry_points.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent_framework.egg-info/requires.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/codex_agent_framework.egg-info/top_level.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/dependencies.txt +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/setup.cfg +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_agent_runtime.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_ai.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_browser.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_builtin_config.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_cli.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_cli_headless.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_cli_root.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_client.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_events.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_get_text_browser.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_hooks.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_image_message.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_local_desktop.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_memory.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_messages.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_planner.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_scheduler.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_service.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_tray.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_tui.py +0 -0
- {codex_agent_framework-0.1.26 → codex_agent_framework-0.1.28}/tests/test_worker.py +0 -0
|
@@ -4,6 +4,23 @@ 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.28] - 2026-05-15
|
|
8
|
+
### Added
|
|
9
|
+
- Add `StreamManager` as a dedicated stream-processing manager and expose it through the package API.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Improve Textual TUI streaming performance with coalesced replay/live deltas and safer incremental rendering.
|
|
13
|
+
- Make terminal resize handling re-render the TUI transcript and status cleanly.
|
|
14
|
+
- Treat the worker status cache as the single status source of truth, with process fallback limited to bootstrap.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Keep `/status`, context providers, and the TUI status bar aligned after worker restarts and busy turns.
|
|
18
|
+
- Preserve wrapper/message partition counters and token splits across context builds, API status updates, and restarts.
|
|
19
|
+
- Avoid status update events confusing stale-turn detection.
|
|
20
|
+
|
|
21
|
+
### Tests
|
|
22
|
+
- Expand regression coverage for status cache replication, fallback bootstrap behavior, wrapper partitions, TUI delta batching, replay coalescing, and resize handling; the current suite validates at 576 passed.
|
|
23
|
+
|
|
7
24
|
## [0.1.25] - 2026-05-12
|
|
8
25
|
### Added
|
|
9
26
|
- Add bundled plugin `requirements.txt` metadata for built-in capabilities and move image generation, realtime voice, and TTS into plugin-scoped packages.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-agent-framework
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.28
|
|
4
4
|
Summary: A lightweight event-driven Codex agent runtime.
|
|
5
5
|
Author: Baptiste
|
|
6
6
|
License-Expression: MIT
|
|
@@ -626,6 +626,7 @@ Core LLM calls, memory embeddings, non-streaming STT transcription, and realtime
|
|
|
626
626
|
|
|
627
627
|
## Recent releases
|
|
628
628
|
|
|
629
|
+
- `0.1.27`: upgrade the agentic `read` tool with folder trees, recursive folder pattern search, and precise `start_at_line`/`end_at_line` ranges; add visible wrapper/token split stats, compactable turn counts, and refreshed TUI status-bar context telemetry.
|
|
629
630
|
- `0.1.26`: align SDK-facing config with Responses API shapes, add the `context` management plugin with selective non-destructive wrapper pruning, keep wrappers out of memory retrieval and compaction payloads, move embeddings/STT/realtime onto Codex backend auth, streamline voice/TTS plugin config, and improve TUI streaming/windowing.
|
|
630
631
|
- `0.1.25`: move built-in capabilities further into plugin-scoped modules, add bundled plugin requirement metadata, and replace the TUI conversation pane with a selectable rich chat area that preserves mouse scrolling, copy selection, lazy transcript loading, and bottom-aware auto-scroll.
|
|
631
632
|
- `0.1.24`: add slash-command lifecycle events and TUI progress/result rendering so commands such as `/compact` and `/help` show running, done, failed, result, and error feedback instead of going silent.
|
|
@@ -577,6 +577,7 @@ Core LLM calls, memory embeddings, non-streaming STT transcription, and realtime
|
|
|
577
577
|
|
|
578
578
|
## Recent releases
|
|
579
579
|
|
|
580
|
+
- `0.1.27`: upgrade the agentic `read` tool with folder trees, recursive folder pattern search, and precise `start_at_line`/`end_at_line` ranges; add visible wrapper/token split stats, compactable turn counts, and refreshed TUI status-bar context telemetry.
|
|
580
581
|
- `0.1.26`: align SDK-facing config with Responses API shapes, add the `context` management plugin with selective non-destructive wrapper pruning, keep wrappers out of memory retrieval and compaction payloads, move embeddings/STT/realtime onto Codex backend auth, streamline voice/TTS plugin config, and improve TUI streaming/windowing.
|
|
581
582
|
- `0.1.25`: move built-in capabilities further into plugin-scoped modules, add bundled plugin requirement metadata, and replace the TUI conversation pane with a selectable rich chat area that preserves mouse scrolling, copy selection, lazy transcript loading, and bottom-aware auto-scroll.
|
|
582
583
|
- `0.1.24`: add slash-command lifecycle events and TUI progress/result rendering so commands such as `/compact` and `/help` show running, done, failed, result, and error feedback instead of going silent.
|
|
@@ -60,7 +60,7 @@ from .image import ImageMessage
|
|
|
60
60
|
from .mainloop import MainLoopManager
|
|
61
61
|
from .plugin import Plugin, PluginsManager, event_handler, hook_handler, stream_processor
|
|
62
62
|
from .command import AgentCommand, AgentFuture, AgentTurn, CommandManager, command
|
|
63
|
-
from .stream import AgentStream
|
|
63
|
+
from .stream import AgentStream, StreamManager
|
|
64
64
|
from .provider import ProvidersManager, provider
|
|
65
65
|
from .runtime import RuntimeManager
|
|
66
66
|
from .sessions import AgentSession, SessionsManager
|
|
@@ -138,6 +138,7 @@ __all__ = [
|
|
|
138
138
|
"AgentFuture",
|
|
139
139
|
"AgentTurn",
|
|
140
140
|
"AgentStream",
|
|
141
|
+
"StreamManager",
|
|
141
142
|
"CommandManager",
|
|
142
143
|
"command",
|
|
143
144
|
"provider",
|
|
@@ -14,6 +14,7 @@ from .runtime import RuntimeManager
|
|
|
14
14
|
from .scheduler import AgentScheduler
|
|
15
15
|
from .sessions import SessionsManager
|
|
16
16
|
from .status import StatusManager
|
|
17
|
+
from .stream import StreamManager
|
|
17
18
|
from .tool import ToolsManager, reset_current_agent, set_current_agent
|
|
18
19
|
from .ai import AIClient
|
|
19
20
|
from .event import AssistantInterruptRequestedEvent, AssistantInterrupted, AssistantInterruptedEvent, EventBus
|
|
@@ -32,26 +33,20 @@ class Agent:
|
|
|
32
33
|
ensure_runtime_dir()
|
|
33
34
|
self.init_execution_environment()
|
|
34
35
|
self.config_manager=ConfigManager(self)
|
|
35
|
-
self.config=self.config_manager.config
|
|
36
36
|
self.config_manager.load()
|
|
37
37
|
self.config_manager.update(kwargs, save=False)
|
|
38
38
|
self.config_manager.apply_openai_capability_defaults()
|
|
39
39
|
self.events=EventBus()
|
|
40
40
|
self.tools_manager=ToolsManager(self)
|
|
41
|
-
self.tools=self.tools_manager.tools
|
|
42
41
|
self.providers_manager=ProvidersManager(self)
|
|
43
|
-
self.providers=self.providers_manager.providers
|
|
44
42
|
self.command_manager=CommandManager(self)
|
|
45
|
-
self.commands=self.command_manager.commands
|
|
46
43
|
self.context_manager=ContextManager(self)
|
|
47
44
|
self.plugins_manager=PluginsManager(self)
|
|
48
|
-
self.plugins=self.plugins_manager.plugins
|
|
49
45
|
self.sessions_manager=SessionsManager(self)
|
|
50
46
|
self.runtime=RuntimeManager(self)
|
|
51
47
|
self.scheduler=AgentScheduler(self)
|
|
52
48
|
self.hooks=HookManager(self)
|
|
53
|
-
self.
|
|
54
|
-
self.stream_processor_owners={}
|
|
49
|
+
self.stream_manager=StreamManager(self)
|
|
55
50
|
self.mainloop_manager=MainLoopManager(self)
|
|
56
51
|
self._interrupt_requested=ThreadEvent()
|
|
57
52
|
self.current_session_id=None
|
|
@@ -86,6 +81,40 @@ class Agent:
|
|
|
86
81
|
mainloop_manager.record_event(event)
|
|
87
82
|
return event
|
|
88
83
|
|
|
84
|
+
@property
|
|
85
|
+
def config(self):
|
|
86
|
+
return self.config_manager.config
|
|
87
|
+
|
|
88
|
+
@config.setter
|
|
89
|
+
def config(self, value):
|
|
90
|
+
self.config_manager.config = value
|
|
91
|
+
if hasattr(self.config_manager, "configs"):
|
|
92
|
+
self.config_manager.configs.agent = value
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def tools(self):
|
|
96
|
+
return self.tools_manager.tools
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def providers(self):
|
|
100
|
+
return self.providers_manager.providers
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def commands(self):
|
|
104
|
+
return self.command_manager.commands
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def plugins(self):
|
|
108
|
+
return self.plugins_manager.plugins
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def stream_processors(self):
|
|
112
|
+
return self.stream_manager.processors
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def stream_processor_owners(self):
|
|
116
|
+
return self.stream_manager.owners
|
|
117
|
+
|
|
89
118
|
@property
|
|
90
119
|
def busy(self):
|
|
91
120
|
return self.mainloop_manager.busy
|
|
@@ -274,32 +303,13 @@ class Agent:
|
|
|
274
303
|
return self.plugins_manager.reload_plugin(plugin_or_name, timeout=timeout)
|
|
275
304
|
|
|
276
305
|
def add_stream_processor(self, key, processor, owner=None):
|
|
277
|
-
|
|
278
|
-
processors.append(processor)
|
|
279
|
-
if owner is not None:
|
|
280
|
-
self.stream_processor_owners.setdefault(str(key), {})[processor] = owner
|
|
281
|
-
return processor
|
|
306
|
+
return self.stream_manager.add(key, processor, owner=owner)
|
|
282
307
|
|
|
283
308
|
def get_stream_processors(self, key):
|
|
284
|
-
return
|
|
309
|
+
return self.stream_manager.get(key)
|
|
285
310
|
|
|
286
311
|
def remove_stream_processors_by_owner(self, owner):
|
|
287
|
-
|
|
288
|
-
for key, processors in list(self.stream_processors.items()):
|
|
289
|
-
owners = self.stream_processor_owners.get(key, {})
|
|
290
|
-
kept = []
|
|
291
|
-
for processor in processors:
|
|
292
|
-
if owners.get(processor) == owner:
|
|
293
|
-
removed.setdefault(key, []).append(processor)
|
|
294
|
-
owners.pop(processor, None)
|
|
295
|
-
else:
|
|
296
|
-
kept.append(processor)
|
|
297
|
-
if kept:
|
|
298
|
-
self.stream_processors[key] = kept
|
|
299
|
-
else:
|
|
300
|
-
self.stream_processors.pop(key, None)
|
|
301
|
-
self.stream_processor_owners.pop(key, None)
|
|
302
|
-
return removed
|
|
312
|
+
return self.stream_manager.remove_by_owner(owner)
|
|
303
313
|
|
|
304
314
|
def add_hook(self, key, func=None, owner=None):
|
|
305
315
|
"""Register a runtime hook.
|
|
@@ -8,8 +8,11 @@ from abc import ABC, abstractmethod
|
|
|
8
8
|
from modict import modict
|
|
9
9
|
|
|
10
10
|
from .agent import Agent
|
|
11
|
+
from .config import AgentConfig
|
|
11
12
|
from .event import Event
|
|
12
|
-
from .message import UserMessage, message_from_data
|
|
13
|
+
from .message import ToolResultWrapper, UserMessage, message_from_data
|
|
14
|
+
from .sessions import AgentSession
|
|
15
|
+
from .utils import token_count, truncate
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
def jsonable(value):
|
|
@@ -270,13 +273,38 @@ class ProcessAgentRuntime(AgentRuntime):
|
|
|
270
273
|
current_turn_id=None,
|
|
271
274
|
current_session_id=None,
|
|
272
275
|
status=modict(),
|
|
273
|
-
config=
|
|
276
|
+
config=AgentConfig(),
|
|
274
277
|
session=modict(),
|
|
275
278
|
sessions=modict(current=None, sessions=[]),
|
|
276
279
|
messages=[],
|
|
277
280
|
tools=modict(),
|
|
278
281
|
wakeups=[],
|
|
279
282
|
)
|
|
283
|
+
self.hydrate_local_session_cache()
|
|
284
|
+
|
|
285
|
+
def local_session_id(self):
|
|
286
|
+
session_id = self.agent_kwargs.get("session")
|
|
287
|
+
if not session_id or session_id == "latest":
|
|
288
|
+
try:
|
|
289
|
+
sessions = AgentSession.list_sessions()
|
|
290
|
+
except Exception:
|
|
291
|
+
return None
|
|
292
|
+
return sessions[0] if sessions else None
|
|
293
|
+
return session_id if AgentSession.can_load(session_id) else None
|
|
294
|
+
|
|
295
|
+
def hydrate_local_session_cache(self):
|
|
296
|
+
session_id = self.local_session_id()
|
|
297
|
+
if not session_id:
|
|
298
|
+
return False
|
|
299
|
+
try:
|
|
300
|
+
session = AgentSession.load(session_id)
|
|
301
|
+
except Exception:
|
|
302
|
+
return False
|
|
303
|
+
self.cache.current_session_id = session.session_id
|
|
304
|
+
self.cache.session = session
|
|
305
|
+
self.cache.messages = list(session.messages)
|
|
306
|
+
self.refresh_cached_wrapper_status(hydrate=False)
|
|
307
|
+
return True
|
|
280
308
|
|
|
281
309
|
def start(self):
|
|
282
310
|
with self.start_lock:
|
|
@@ -293,6 +321,7 @@ class ProcessAgentRuntime(AgentRuntime):
|
|
|
293
321
|
self.process.start()
|
|
294
322
|
self.event_thread = threading.Thread(target=self._event_loop, name="agent-runtime-events", daemon=True)
|
|
295
323
|
self.event_thread.start()
|
|
324
|
+
self.hydrate_local_session_cache()
|
|
296
325
|
try:
|
|
297
326
|
self.call("start", timeout=self.command_timeout, autostart=False)
|
|
298
327
|
self.refresh_cache()
|
|
@@ -339,6 +368,8 @@ class ProcessAgentRuntime(AgentRuntime):
|
|
|
339
368
|
except Exception:
|
|
340
369
|
pass
|
|
341
370
|
self.update_status_cache(self.cache.get("status") or {})
|
|
371
|
+
if not self.cache.get("status"):
|
|
372
|
+
self.refresh_cached_wrapper_status()
|
|
342
373
|
return self.cache
|
|
343
374
|
|
|
344
375
|
def call(self, name, payload=None, timeout=None, autostart=True):
|
|
@@ -391,6 +422,107 @@ class ProcessAgentRuntime(AgentRuntime):
|
|
|
391
422
|
self.cache.current_session_id = status.get("session_id", self.cache.current_session_id)
|
|
392
423
|
return status
|
|
393
424
|
|
|
425
|
+
def cached_message_from_event(self, message):
|
|
426
|
+
if not message:
|
|
427
|
+
return None
|
|
428
|
+
try:
|
|
429
|
+
return message_from_data(message)
|
|
430
|
+
except Exception:
|
|
431
|
+
return modict(message)
|
|
432
|
+
|
|
433
|
+
def is_cached_wrapper(self, message):
|
|
434
|
+
if isinstance(message, ToolResultWrapper):
|
|
435
|
+
return True
|
|
436
|
+
types = message.get("types") or []
|
|
437
|
+
return message.get("type") == "tool_result_wrapper" or "tool_result_wrapper" in types
|
|
438
|
+
|
|
439
|
+
def cached_wrapper_tokens(self, messages):
|
|
440
|
+
total = 0
|
|
441
|
+
config = modict(self.cache.get("config") or {})
|
|
442
|
+
max_tokens = int(config.get("max_input_tokens", 8000) or 8000)
|
|
443
|
+
model = config.get("model")
|
|
444
|
+
for message in messages:
|
|
445
|
+
content = message.get("content")
|
|
446
|
+
if not isinstance(content, str) or not content:
|
|
447
|
+
continue
|
|
448
|
+
total += token_count(truncate(content, max_tokens=max_tokens, model=model), model=model)
|
|
449
|
+
return total
|
|
450
|
+
|
|
451
|
+
def refresh_cached_wrapper_status(self, hydrate=True):
|
|
452
|
+
status = modict(self.cache.status or {})
|
|
453
|
+
if hydrate and not self.cache.get("messages"):
|
|
454
|
+
self.hydrate_local_session_cache()
|
|
455
|
+
messages = list(self.cache.get("messages") or [])
|
|
456
|
+
wrappers = [message for message in messages if self.is_cached_wrapper(message)]
|
|
457
|
+
active = [
|
|
458
|
+
message for message in wrappers
|
|
459
|
+
if isinstance(message.get("turns_left", -1), int) and message.get("turns_left", -1) > 0
|
|
460
|
+
]
|
|
461
|
+
vanished = [message for message in wrappers if message.get("turns_left", -1) == 0]
|
|
462
|
+
active_tokens = self.cached_wrapper_tokens(active)
|
|
463
|
+
vanished_tokens = self.cached_wrapper_tokens(vanished)
|
|
464
|
+
visible_non_wrappers = [
|
|
465
|
+
message for message in messages
|
|
466
|
+
if not self.is_cached_wrapper(message) and message.get("turns_left", -1) != 0
|
|
467
|
+
]
|
|
468
|
+
estimated_base_tokens = self.cached_wrapper_tokens(visible_non_wrappers)
|
|
469
|
+
# The fallback cache is updated by message events while the worker may be busy. Do not preserve
|
|
470
|
+
# a stale worker-provided context_current_tokens here: otherwise wrapper/message counts move but
|
|
471
|
+
# the context percentage appears frozen until a full worker status succeeds.
|
|
472
|
+
current_tokens = estimated_base_tokens + active_tokens
|
|
473
|
+
persistent = [message for message in messages if message.get("turns_left", -1) == -1]
|
|
474
|
+
temporary = [
|
|
475
|
+
message for message in messages
|
|
476
|
+
if isinstance(message.get("turns_left", -1), int) and message.get("turns_left", -1) > 0
|
|
477
|
+
]
|
|
478
|
+
config = modict(self.cache.get("config") or {})
|
|
479
|
+
input_limit = int(status.get("context_limit_tokens") or config.get("input_token_limit", 0) or 0)
|
|
480
|
+
if not current_tokens:
|
|
481
|
+
current_tokens = estimated_base_tokens + active_tokens
|
|
482
|
+
base_tokens = max(0, current_tokens - active_tokens) if current_tokens else estimated_base_tokens
|
|
483
|
+
status.update(
|
|
484
|
+
context_current_tokens=current_tokens,
|
|
485
|
+
context_limit_tokens=input_limit,
|
|
486
|
+
context_percent=(current_tokens / input_limit * 100) if input_limit else 0,
|
|
487
|
+
model=status.get("model") or config.get("model"),
|
|
488
|
+
source="process_fallback",
|
|
489
|
+
total_session_messages=len(messages) or status.get("total_session_messages", 0),
|
|
490
|
+
persistent_session_messages=len(persistent),
|
|
491
|
+
temporary_session_messages=len(temporary),
|
|
492
|
+
active_wrapper_messages=len(active),
|
|
493
|
+
vanished_wrapper_messages=len(vanished),
|
|
494
|
+
total_wrapper_messages=len(wrappers),
|
|
495
|
+
active_wrapper_tokens=active_tokens,
|
|
496
|
+
vanished_wrapper_tokens=vanished_tokens,
|
|
497
|
+
base_context_tokens=base_tokens,
|
|
498
|
+
)
|
|
499
|
+
self.cache.status = status
|
|
500
|
+
return status
|
|
501
|
+
|
|
502
|
+
def has_worker_status_cache(self):
|
|
503
|
+
status = modict(self.cache.get("status") or {})
|
|
504
|
+
source = status.get("source")
|
|
505
|
+
return bool(source) and source != "process_fallback"
|
|
506
|
+
|
|
507
|
+
def append_cached_message(self, message):
|
|
508
|
+
message = self.cached_message_from_event(message)
|
|
509
|
+
if message is None:
|
|
510
|
+
return None
|
|
511
|
+
self.cache.messages.append(message)
|
|
512
|
+
if not self.has_worker_status_cache():
|
|
513
|
+
return self.refresh_cached_wrapper_status()
|
|
514
|
+
return self.cache.status
|
|
515
|
+
|
|
516
|
+
def age_cached_context_messages(self):
|
|
517
|
+
changed = False
|
|
518
|
+
for message in self.cache.get("messages") or []:
|
|
519
|
+
turns_left = message.get("turns_left", -1)
|
|
520
|
+
if isinstance(turns_left, int) and turns_left > 0:
|
|
521
|
+
message.turns_left = turns_left - 1
|
|
522
|
+
changed = True
|
|
523
|
+
if changed and not self.has_worker_status_cache():
|
|
524
|
+
self.refresh_cached_wrapper_status()
|
|
525
|
+
|
|
394
526
|
def mark_turn_started(self, turn_id=None):
|
|
395
527
|
self.cache.busy = True
|
|
396
528
|
if turn_id is not None:
|
|
@@ -404,12 +536,22 @@ class ProcessAgentRuntime(AgentRuntime):
|
|
|
404
536
|
event_type = event.get("type")
|
|
405
537
|
if event_type == "assistant_turn_start_event":
|
|
406
538
|
self.mark_turn_started(event.get("turn_id"))
|
|
539
|
+
self.age_cached_context_messages()
|
|
407
540
|
elif event_type in ("assistant_turn_end_event", "assistant_turn_error_event"):
|
|
408
541
|
self.mark_turn_finished()
|
|
542
|
+
elif event_type == "message_added_event":
|
|
543
|
+
message = self.cached_message_from_event(event.get("message"))
|
|
544
|
+
if message is not None:
|
|
545
|
+
self.cache.messages.append(message)
|
|
546
|
+
if not self.has_worker_status_cache():
|
|
547
|
+
self.refresh_cached_wrapper_status()
|
|
409
548
|
elif event_type == "session_loaded_event":
|
|
410
549
|
self.cache.current_session_id = event.get("session_id")
|
|
411
550
|
self.cache.session = event.get("session") or self.cache.session
|
|
412
|
-
|
|
551
|
+
self.cache.messages = list((self.cache.session or {}).get("messages") or [])
|
|
552
|
+
if not self.has_worker_status_cache():
|
|
553
|
+
self.refresh_cached_wrapper_status()
|
|
554
|
+
elif event_type in ("status_updated_event", "compaction_completed_event") and event.get("status"):
|
|
413
555
|
self.update_status_cache(event.status)
|
|
414
556
|
for handler in list(self.event_handlers):
|
|
415
557
|
try:
|
|
@@ -434,8 +576,13 @@ class ProcessAgentRuntime(AgentRuntime):
|
|
|
434
576
|
return self.update_status_cache(self.call("get_status", timeout=self.status_timeout))
|
|
435
577
|
except Exception:
|
|
436
578
|
status = modict(self.cache.status or {})
|
|
437
|
-
|
|
438
|
-
|
|
579
|
+
if not self.has_worker_status_cache():
|
|
580
|
+
status = self.refresh_cached_wrapper_status()
|
|
581
|
+
status.update(
|
|
582
|
+
busy=self.cache.busy,
|
|
583
|
+
current_turn_id=self.cache.current_turn_id,
|
|
584
|
+
session_id=self.cache.current_session_id,
|
|
585
|
+
)
|
|
439
586
|
return status
|
|
440
587
|
|
|
441
588
|
def config(self):
|
|
@@ -33,6 +33,15 @@ Tool result wrappers expose stable `wrapper_id` values in their XML representati
|
|
|
33
33
|
total_history_messages=status.get("total_history_messages", 0),
|
|
34
34
|
persistent_session_messages=status.get("persistent_session_messages", 0),
|
|
35
35
|
temporary_session_messages=status.get("temporary_session_messages", 0),
|
|
36
|
+
active_wrapper_messages=status.get("active_wrapper_messages", 0),
|
|
37
|
+
vanished_wrapper_messages=status.get("vanished_wrapper_messages", 0),
|
|
38
|
+
total_wrapper_messages=status.get("total_wrapper_messages", 0),
|
|
39
|
+
base_context_tokens=status.get("base_context_tokens", status.used_tokens),
|
|
40
|
+
active_wrapper_tokens=status.get("active_wrapper_tokens", 0),
|
|
41
|
+
vanished_wrapper_tokens=status.get("vanished_wrapper_tokens", 0),
|
|
42
|
+
compactable_turns=status.get("compactable_turns", 0),
|
|
43
|
+
compactable_messages=status.get("compactable_messages", 0),
|
|
44
|
+
compactable_tokens=status.get("compactable_tokens", 0),
|
|
36
45
|
context_session_messages=status.get("context_session_messages", 0),
|
|
37
46
|
image_messages=status.get("image_messages", 0),
|
|
38
47
|
pending_messages=status.get("pending_messages", 0),
|
|
@@ -48,8 +57,11 @@ Tool result wrappers expose stable `wrapper_id` values in their XML representati
|
|
|
48
57
|
return "\n".join([
|
|
49
58
|
"Context status:",
|
|
50
59
|
f"- tokens: {status.used_tokens}/{status.input_token_limit} ({status.percent:.2f}%)",
|
|
60
|
+
f"- token split base/active_wrappers/vanished_wrappers: {status.base_context_tokens}/{status.active_wrapper_tokens}/{status.vanished_wrapper_tokens}",
|
|
51
61
|
f"- messages sent/total: {status.sent_session_messages}/{status.total_session_messages}",
|
|
52
62
|
f"- persistent/temporary context messages: {status.persistent_session_messages}/{status.temporary_session_messages}",
|
|
63
|
+
f"- wrappers active/vanished/total: {status.active_wrapper_messages}/{status.vanished_wrapper_messages}/{status.total_wrapper_messages}",
|
|
64
|
+
f"- compactable turns/messages/tokens: {status.compactable_turns}/{status.compactable_messages}/{status.compactable_tokens}",
|
|
53
65
|
f"- visible/total history messages: {status.visible_history_messages}/{status.total_history_messages}",
|
|
54
66
|
f"- tool spec tokens: {status.tool_spec_tokens}",
|
|
55
67
|
f"- auto_compact: {status.auto_compact} threshold={status.auto_compact_threshold}",
|
|
@@ -7,13 +7,13 @@ from . import tools as file_tools
|
|
|
7
7
|
|
|
8
8
|
class FilesPlugin(Plugin):
|
|
9
9
|
name = "files"
|
|
10
|
-
description = "Strict local UTF-8 file read
|
|
10
|
+
description = "Strict local UTF-8 file/folder read and search plus write/edit operations."
|
|
11
11
|
prefix_tools = False
|
|
12
12
|
system_prompt = """
|
|
13
13
|
# File Tools
|
|
14
14
|
|
|
15
|
-
Use the specialized file tools for local UTF-8 text
|
|
16
|
-
- `read`: read line-numbered local UTF-8 files precisely, with
|
|
15
|
+
Use the specialized file tools for local UTF-8 text operations:
|
|
16
|
+
- `read`: read line-numbered local UTF-8 files precisely, list local folders as a compact one-level tree, or search folders recursively with a pattern.
|
|
17
17
|
- `write`: create or overwrite complete UTF-8 text files; use with caution.
|
|
18
18
|
- `edit`: perform targeted exact-string replacements in local UTF-8 text files.
|
|
19
19
|
|
|
@@ -8,10 +8,11 @@ from ...utils import read_document_content, view_document_content
|
|
|
8
8
|
def read(reads):
|
|
9
9
|
"""
|
|
10
10
|
description: |
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
Read one or more local UTF-8 text files or folders as source-of-truth content.
|
|
12
|
+
File reads always prefix returned content with reliable source line numbers.
|
|
13
|
+
Folder reads without `pattern` render a compact tree with one level of recursion.
|
|
14
|
+
Folder reads with `pattern` recursively search UTF-8 files under the folder and return numbered snippets per matching file.
|
|
15
|
+
This tool does not extract document formats, fetch URLs, or inspect Python objects; use `view` for URLs, PDFs, DOCX, ODT, HTML, XLSX, archives, and other extracted or transformed content.
|
|
15
16
|
|
|
16
17
|
For large outputs: the output is automatically truncated. When truncated, a message indicates the omitted line range.
|
|
17
18
|
When `pattern` is provided, it is treated as a Python regex by default and all matches are returned as separated numbered snippets with up to 10 lines before and after each match.
|
|
@@ -20,16 +21,19 @@ def read(reads):
|
|
|
20
21
|
reads:
|
|
21
22
|
description: |
|
|
22
23
|
A single read object or a list of read objects.
|
|
23
|
-
Each object must contain `source` and may contain `start_at_line`, `pattern`, and `regex`.
|
|
24
|
+
Each object must contain `source` and may contain `start_at_line`, `end_at_line`, `pattern`, and `regex`.
|
|
24
25
|
anyOf:
|
|
25
26
|
- type: object
|
|
26
27
|
properties:
|
|
27
28
|
source:
|
|
28
|
-
description: Local UTF-8 text file path
|
|
29
|
+
description: Local UTF-8 text file or folder path. Folders are listed as a compact tree, or searched recursively when pattern is provided. For URLs, rich documents, archives, or Python objects, use `view` instead.
|
|
29
30
|
type: string
|
|
30
31
|
start_at_line:
|
|
31
32
|
description: Line number to start reading from (1-indexed). Default is 1 (start from beginning).
|
|
32
33
|
type: integer
|
|
34
|
+
end_at_line:
|
|
35
|
+
description: Optional inclusive line number to stop reading at for direct file reads.
|
|
36
|
+
type: integer
|
|
33
37
|
pattern:
|
|
34
38
|
description: Optional search pattern. Treated as a Python regular expression by default.
|
|
35
39
|
type: string
|
|
@@ -43,9 +47,12 @@ def read(reads):
|
|
|
43
47
|
type: object
|
|
44
48
|
properties:
|
|
45
49
|
source:
|
|
50
|
+
description: Local UTF-8 text file or folder path. Folders are listed as a compact tree, or searched recursively when pattern is provided.
|
|
46
51
|
type: string
|
|
47
52
|
start_at_line:
|
|
48
53
|
type: integer
|
|
54
|
+
end_at_line:
|
|
55
|
+
type: integer
|
|
49
56
|
pattern:
|
|
50
57
|
type: string
|
|
51
58
|
regex:
|
|
@@ -79,6 +86,7 @@ def apply_file_read(item):
|
|
|
79
86
|
return read_document_content(
|
|
80
87
|
source,
|
|
81
88
|
start_at_line=item.get("start_at_line", 1),
|
|
89
|
+
end_at_line=item.get("end_at_line"),
|
|
82
90
|
max_tokens=max_tokens,
|
|
83
91
|
pattern=item.get("pattern"),
|
|
84
92
|
regex=item.get("regex", True),
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Built-in local interface plugin."""
|
|
2
|
+
|
|
3
|
+
from ...plugin import Plugin
|
|
4
|
+
from ...provider import provider
|
|
5
|
+
from ...tool import tool
|
|
6
|
+
from ...tui import current_tui, open_tui_terminal, terminate_tui
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InterfacePlugin(Plugin):
|
|
10
|
+
name = "interface"
|
|
11
|
+
description = "Open and close the local TUI interface."
|
|
12
|
+
prefix_tools = False
|
|
13
|
+
|
|
14
|
+
@provider
|
|
15
|
+
def tui_status(self):
|
|
16
|
+
"""Expose whether the local TUI client is currently open."""
|
|
17
|
+
tui = current_tui()
|
|
18
|
+
if not tui:
|
|
19
|
+
return "TUI status: closed."
|
|
20
|
+
return (
|
|
21
|
+
"TUI status: open. "
|
|
22
|
+
f"pid={tui.get('pid')} "
|
|
23
|
+
f"base_url={tui.get('base_url')} "
|
|
24
|
+
f"timestamp={tui.get('timestamp')}"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@tool(name="open_tui")
|
|
28
|
+
def open_tui(self):
|
|
29
|
+
"""
|
|
30
|
+
description: Open the Textual chat TUI client in a separate terminal connected to the local agent server.
|
|
31
|
+
parameters: {}
|
|
32
|
+
required: []
|
|
33
|
+
"""
|
|
34
|
+
tui = current_tui()
|
|
35
|
+
if tui:
|
|
36
|
+
return {"status": "already_open", "tui": tui}
|
|
37
|
+
process = open_tui_terminal(base_url=self.agent.config.get("server_url", "http://127.0.0.1:8765"))
|
|
38
|
+
if process is None:
|
|
39
|
+
raise RuntimeError("No supported terminal emulator found to open the TUI")
|
|
40
|
+
return {"status": "opened", "pid": process.pid}
|
|
41
|
+
|
|
42
|
+
@tool(name="close_tui")
|
|
43
|
+
def close_tui(self):
|
|
44
|
+
"""
|
|
45
|
+
description: Close the currently registered Textual chat TUI client process.
|
|
46
|
+
parameters: {}
|
|
47
|
+
required: []
|
|
48
|
+
"""
|
|
49
|
+
tui = current_tui()
|
|
50
|
+
if not tui:
|
|
51
|
+
return {"status": "not_open"}
|
|
52
|
+
terminate_tui(tui.pid)
|
|
53
|
+
return {"status": "closed", "tui": tui}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from modict import modict
|
|
2
2
|
|
|
3
3
|
from .image import ImageMessage
|
|
4
|
-
from .message import Message, ProviderMessage, SystemMessage
|
|
4
|
+
from .message import Message, ProviderMessage, SystemMessage, ToolResultWrapper
|
|
5
5
|
from .tool import reset_current_agent, set_current_agent
|
|
6
6
|
from .utils import sort, token_count_payload, truncate
|
|
7
7
|
|
|
@@ -100,14 +100,45 @@ class ContextManager:
|
|
|
100
100
|
formatted_context = list(msg.format(context=self.agent.context_namespace) for msg in context)
|
|
101
101
|
total_session_messages = len(self.agent.session.messages) if self.agent.session else 0
|
|
102
102
|
sent_session_messages = len(history) + len(images)
|
|
103
|
+
all_session_messages = list(self.agent.session.messages) if self.agent.session else []
|
|
103
104
|
persistent_session_messages = sum(
|
|
104
|
-
1 for msg in
|
|
105
|
+
1 for msg in all_session_messages
|
|
105
106
|
if msg.get("turns_left", -1) == -1
|
|
106
107
|
)
|
|
107
108
|
temporary_session_messages = sum(
|
|
108
|
-
1 for msg in
|
|
109
|
+
1 for msg in all_session_messages
|
|
109
110
|
if isinstance(msg.get("turns_left", -1), int) and msg.get("turns_left", -1) > 0
|
|
110
111
|
)
|
|
112
|
+
wrappers = [msg for msg in all_session_messages if isinstance(msg, ToolResultWrapper)]
|
|
113
|
+
active_wrappers = [
|
|
114
|
+
msg for msg in wrappers
|
|
115
|
+
if isinstance(msg.get("turns_left", -1), int) and msg.get("turns_left", -1) > 0
|
|
116
|
+
]
|
|
117
|
+
vanished_wrappers = [msg for msg in wrappers if msg.get("turns_left", -1) == 0]
|
|
118
|
+
|
|
119
|
+
def wrapper_token_count(messages):
|
|
120
|
+
total = 0
|
|
121
|
+
for msg in messages:
|
|
122
|
+
total += self.response_input_token_count([msg], response_tools=[])
|
|
123
|
+
return total
|
|
124
|
+
|
|
125
|
+
active_context_wrappers = [msg for msg in history if isinstance(msg, ToolResultWrapper)]
|
|
126
|
+
active_wrapper_tokens = wrapper_token_count(active_context_wrappers)
|
|
127
|
+
vanished_wrapper_tokens = wrapper_token_count(vanished_wrappers)
|
|
128
|
+
base_context_tokens = max(0, current_count - active_wrapper_tokens)
|
|
129
|
+
if self.agent.session:
|
|
130
|
+
compaction_plan = self.agent.session.compaction_plan(
|
|
131
|
+
max_input_tokens=max_input_tokens,
|
|
132
|
+
model=self.agent.config.model,
|
|
133
|
+
max_compacted_turns=None,
|
|
134
|
+
)
|
|
135
|
+
compactable_turns = compaction_plan.completed_turn_count
|
|
136
|
+
compactable_messages = compaction_plan.compacted_message_count
|
|
137
|
+
compactable_tokens = compaction_plan.compacted_token_count
|
|
138
|
+
else:
|
|
139
|
+
compactable_turns = 0
|
|
140
|
+
compactable_messages = 0
|
|
141
|
+
compactable_tokens = 0
|
|
111
142
|
token_count_source = "local_estimate"
|
|
112
143
|
try:
|
|
113
144
|
current_count = self.agent.ai.count_input_tokens(
|
|
@@ -125,6 +156,15 @@ class ContextManager:
|
|
|
125
156
|
sent_session_message_percent=(sent_session_messages / total_session_messages * 100) if total_session_messages else 0,
|
|
126
157
|
persistent_session_messages=persistent_session_messages,
|
|
127
158
|
temporary_session_messages=temporary_session_messages,
|
|
159
|
+
active_wrapper_messages=len(active_wrappers),
|
|
160
|
+
vanished_wrapper_messages=len(vanished_wrappers),
|
|
161
|
+
total_wrapper_messages=len(wrappers),
|
|
162
|
+
base_context_tokens=base_context_tokens,
|
|
163
|
+
active_wrapper_tokens=active_wrapper_tokens,
|
|
164
|
+
vanished_wrapper_tokens=vanished_wrapper_tokens,
|
|
165
|
+
compactable_turns=compactable_turns,
|
|
166
|
+
compactable_messages=compactable_messages,
|
|
167
|
+
compactable_tokens=compactable_tokens,
|
|
128
168
|
token_count_source=token_count_source,
|
|
129
169
|
))
|
|
130
170
|
hook = self.agent.run_hook("context.build.after", messages=formatted_context)
|
|
@@ -396,9 +396,13 @@ class AgentServer:
|
|
|
396
396
|
|
|
397
397
|
def event_stream_status(self, stale_timeout=120):
|
|
398
398
|
with self.event_lock:
|
|
399
|
-
last_event = self.events[-1] if self.events else None
|
|
400
399
|
active = bool(self.turn_replay_active)
|
|
401
400
|
current_turn_events = list(self.current_turn_events)
|
|
401
|
+
source_events = current_turn_events if current_turn_events else list(self.events)
|
|
402
|
+
last_event = next(
|
|
403
|
+
(event for event in reversed(source_events) if event.get("type") != "status_updated_event"),
|
|
404
|
+
None,
|
|
405
|
+
)
|
|
402
406
|
last_timestamp = self.event_timestamp(last_event)
|
|
403
407
|
now = time.time()
|
|
404
408
|
return modict(
|