codex-agent-framework 0.1.27__tar.gz → 0.1.29__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 (138) hide show
  1. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/CHANGELOG.md +30 -0
  2. {codex_agent_framework-0.1.27/codex_agent_framework.egg-info → codex_agent_framework-0.1.29}/PKG-INFO +1 -1
  3. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/__init__.py +2 -1
  4. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/agent.py +39 -29
  5. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/agent_runtime.py +152 -5
  6. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/context.py +43 -3
  7. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/event.py +4 -0
  8. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/server/core.py +5 -1
  9. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/status.py +51 -38
  10. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/stream.py +40 -0
  11. codex_agent_framework-0.1.29/codex_agent/tui/app.py +211 -0
  12. codex_agent_framework-0.1.29/codex_agent/tui/chat.py +343 -0
  13. codex_agent_framework-0.1.29/codex_agent/tui/log.py +225 -0
  14. codex_agent_framework-0.1.29/codex_agent/tui/state.py +148 -0
  15. codex_agent_framework-0.1.29/codex_agent/tui/status_bar.py +86 -0
  16. codex_agent_framework-0.1.29/codex_agent/tui/style.py +56 -0
  17. codex_agent_framework-0.1.29/codex_agent/tui/transcript.py +337 -0
  18. codex_agent_framework-0.1.29/codex_agent/version.py +1 -0
  19. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29/codex_agent_framework.egg-info}/PKG-INFO +1 -1
  20. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent_framework.egg-info/SOURCES.txt +6 -0
  21. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/pyproject.toml +1 -1
  22. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_agent.py +38 -0
  23. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_chat.py +350 -117
  24. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_server.py +169 -0
  25. codex_agent_framework-0.1.27/codex_agent/tui/chat.py +0 -1058
  26. codex_agent_framework-0.1.27/codex_agent/version.py +0 -1
  27. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/LICENSE +0 -0
  28. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/MANIFEST.in +0 -0
  29. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/README.md +0 -0
  30. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/__main__.py +0 -0
  31. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/ai.py +0 -0
  32. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/browser.py +0 -0
  33. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_commands.py +0 -0
  34. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/__init__.py +0 -0
  35. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/bash/__init__.py +0 -0
  36. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/bash/requirements.txt +0 -0
  37. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/browser/__init__.py +0 -0
  38. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/browser/requirements.txt +0 -0
  39. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/content/__init__.py +0 -0
  40. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/content/requirements.txt +0 -0
  41. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/content/system.py +0 -0
  42. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/content/vision.py +0 -0
  43. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/context/__init__.py +0 -0
  44. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/desktop/__init__.py +0 -0
  45. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/desktop/requirements.txt +0 -0
  46. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/environment/__init__.py +0 -0
  47. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/environment/requirements.txt +0 -0
  48. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/files/__init__.py +0 -0
  49. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/files/requirements.txt +0 -0
  50. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/files/tools.py +0 -0
  51. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/image_generation/__init__.py +0 -0
  52. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/image_generation/image_generation_system_prompt.txt +0 -0
  53. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/image_generation/requirements.txt +0 -0
  54. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/interface/__init__.py +0 -0
  55. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/interface/requirements.txt +0 -0
  56. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/memory/__init__.py +0 -0
  57. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/memory/requirements.txt +0 -0
  58. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/planner/__init__.py +0 -0
  59. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/planner/requirements.txt +0 -0
  60. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/python/__init__.py +0 -0
  61. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/python/requirements.txt +0 -0
  62. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/realtime/__init__.py +0 -0
  63. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/realtime/audio.py +0 -0
  64. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/realtime/bridge.py +0 -0
  65. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/realtime/manager.py +0 -0
  66. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/realtime/requirements.txt +0 -0
  67. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/realtime/session.py +0 -0
  68. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/scheduler/__init__.py +0 -0
  69. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/scheduler/requirements.txt +0 -0
  70. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/subagents/__init__.py +0 -0
  71. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/subagents/profiles/explorer.json +0 -0
  72. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/subagents/requirements.txt +0 -0
  73. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/tts/__init__.py +0 -0
  74. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/builtin_plugins/tts/requirements.txt +0 -0
  75. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/cli/__init__.py +0 -0
  76. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/cli/headless.py +0 -0
  77. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/cli/main.py +0 -0
  78. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/cli/output.py +0 -0
  79. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/cli/root.py +0 -0
  80. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/cli/runner.py +0 -0
  81. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/client.py +0 -0
  82. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/command.py +0 -0
  83. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/config.py +0 -0
  84. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/desktop.py +0 -0
  85. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/get_text/__init__.py +0 -0
  86. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/get_text/default_gitignore +0 -0
  87. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/get_text/get_text.py +0 -0
  88. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/get_text/simpler_get_text.py +0 -0
  89. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/hooks.py +0 -0
  90. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/image.py +0 -0
  91. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/latex.py +0 -0
  92. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/mainloop.py +0 -0
  93. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/message.py +0 -0
  94. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/plugin.py +0 -0
  95. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/prompts/system_prompt.txt +0 -0
  96. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/provider.py +0 -0
  97. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/runtime.py +0 -0
  98. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/scheduler.py +0 -0
  99. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/scripts/install-system-dependencies.sh +0 -0
  100. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/server/__init__.py +0 -0
  101. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/server/app.py +0 -0
  102. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/service.py +0 -0
  103. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/sessions.py +0 -0
  104. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/stream_utils.py +0 -0
  105. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/tool.py +0 -0
  106. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/tray.py +0 -0
  107. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/tui/__init__.py +0 -0
  108. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/tui/lifecycle.py +0 -0
  109. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/utils.py +0 -0
  110. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent/worker.py +0 -0
  111. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
  112. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent_framework.egg-info/entry_points.txt +0 -0
  113. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent_framework.egg-info/requires.txt +0 -0
  114. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/codex_agent_framework.egg-info/top_level.txt +0 -0
  115. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/dependencies.txt +0 -0
  116. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/setup.cfg +0 -0
  117. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_agent_runtime.py +0 -0
  118. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_ai.py +0 -0
  119. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_browser.py +0 -0
  120. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_builtin_config.py +0 -0
  121. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_cli.py +0 -0
  122. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_cli_headless.py +0 -0
  123. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_cli_root.py +0 -0
  124. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_client.py +0 -0
  125. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_events.py +0 -0
  126. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_get_text_browser.py +0 -0
  127. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_hooks.py +0 -0
  128. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_image_message.py +0 -0
  129. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_local_desktop.py +0 -0
  130. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_memory.py +0 -0
  131. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_messages.py +0 -0
  132. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_planner.py +0 -0
  133. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_scheduler.py +0 -0
  134. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_service.py +0 -0
  135. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_tray.py +0 -0
  136. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_tui.py +0 -0
  137. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_utils.py +0 -0
  138. {codex_agent_framework-0.1.27 → codex_agent_framework-0.1.29}/tests/test_worker.py +0 -0
@@ -4,6 +4,36 @@ 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.29] - 2026-05-15
8
+ ### Changed
9
+ - Split the Textual TUI implementation into focused modules for app wiring, transcript rendering, status display, styling, log windowing, and render state.
10
+ - Replace ad hoc TUI app calls and duplicate display tracking with a typed bridge and centralized deduplication state.
11
+ - Move terminal resize handling into the Textual app so transcript, input sizing, layout cache, and status refresh stay coordinated.
12
+
13
+ ### Fixed
14
+ - Invalidate selectable log layout metrics cleanly on terminal resize to keep wrapped transcript rendering accurate.
15
+ - Keep TUI turn rendering state scoped to the active turn without leaking tool or slash-command display markers.
16
+
17
+ ### Tests
18
+ - Update TUI/chat regression coverage for the extracted bridge/state modules and resize handling; the current suite validates at 574 passed.
19
+
20
+ ## [0.1.28] - 2026-05-15
21
+ ### Added
22
+ - Add `StreamManager` as a dedicated stream-processing manager and expose it through the package API.
23
+
24
+ ### Changed
25
+ - Improve Textual TUI streaming performance with coalesced replay/live deltas and safer incremental rendering.
26
+ - Make terminal resize handling re-render the TUI transcript and status cleanly.
27
+ - Treat the worker status cache as the single status source of truth, with process fallback limited to bootstrap.
28
+
29
+ ### Fixed
30
+ - Keep `/status`, context providers, and the TUI status bar aligned after worker restarts and busy turns.
31
+ - Preserve wrapper/message partition counters and token splits across context builds, API status updates, and restarts.
32
+ - Avoid status update events confusing stale-turn detection.
33
+
34
+ ### Tests
35
+ - 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.
36
+
7
37
  ## [0.1.25] - 2026-05-12
8
38
  ### Added
9
39
  - 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.27
3
+ Version: 0.1.29
4
4
  Summary: A lightweight event-driven Codex agent runtime.
5
5
  Author: Baptiste
6
6
  License-Expression: MIT
@@ -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.stream_processors=modict()
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
- processors = self.stream_processors.setdefault(str(key), [])
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 list(self.stream_processors.get(str(key), []))
309
+ return self.stream_manager.get(key)
285
310
 
286
311
  def remove_stream_processors_by_owner(self, owner):
287
- removed = modict()
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=modict(),
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
- elif event_type == "compaction_completed_event" and event.get("status"):
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
- status.setdefault("busy", self.cache.busy)
438
- status.setdefault("current_turn_id", self.cache.current_turn_id)
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):
@@ -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 session_messages
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 session_messages
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)
@@ -102,6 +102,10 @@ class MessageAddedEvent(Event):
102
102
  message = None
103
103
 
104
104
 
105
+ class StatusUpdatedEvent(Event):
106
+ status = None
107
+
108
+
105
109
  class MessageSubmittedEvent(Event):
106
110
  message = None
107
111
  turn_id = None
@@ -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(
@@ -1,5 +1,6 @@
1
1
  from modict import modict
2
2
 
3
+ from .event import StatusUpdatedEvent
3
4
  from .image import ImageMessage
4
5
  from .message import ToolResultWrapper
5
6
  from .utils import sort, truncate
@@ -12,11 +13,48 @@ class StatusManager:
12
13
  self.context_status_cache = None
13
14
  self.wham_status_loaded = False
14
15
 
16
+ def publish_status_cache(self):
17
+ if self.status_cache is not None:
18
+ self.agent.emit(StatusUpdatedEvent, status=self.status_cache)
19
+ return self.status_cache
20
+
21
+ def status_from_context_status(self, context_status):
22
+ previous = modict(self.status_cache or {})
23
+ return modict(
24
+ context_current_tokens=context_status.used_tokens,
25
+ context_limit_tokens=context_status.input_token_limit,
26
+ context_percent=context_status.percent,
27
+ sent_session_messages=context_status.sent_session_messages,
28
+ total_session_messages=context_status.total_session_messages,
29
+ sent_session_message_percent=context_status.sent_session_message_percent,
30
+ persistent_session_messages=context_status.get("persistent_session_messages", 0),
31
+ temporary_session_messages=context_status.get("temporary_session_messages", 0),
32
+ active_wrapper_messages=context_status.get("active_wrapper_messages", 0),
33
+ vanished_wrapper_messages=context_status.get("vanished_wrapper_messages", 0),
34
+ total_wrapper_messages=context_status.get("total_wrapper_messages", 0),
35
+ base_context_tokens=context_status.get("base_context_tokens", context_status.used_tokens),
36
+ active_wrapper_tokens=context_status.get("active_wrapper_tokens", 0),
37
+ vanished_wrapper_tokens=context_status.get("vanished_wrapper_tokens", 0),
38
+ compactable_turns=context_status.get("compactable_turns", 0),
39
+ compactable_messages=context_status.get("compactable_messages", 0),
40
+ compactable_tokens=context_status.get("compactable_tokens", 0),
41
+ quota_5h_percent=previous.get("quota_5h_percent"),
42
+ quota_7d_percent=previous.get("quota_7d_percent"),
43
+ model=previous.get("model") or self.agent.config.get("model") or "gpt-5.4",
44
+ busy=self.agent.mainloop_manager.busy,
45
+ current_turn_id=self.agent.mainloop_manager.current_turn_id,
46
+ session_id=self.agent.current_session_id,
47
+ source=context_status.get("token_count_source", "estimated"),
48
+ activity=self.agent.mainloop_manager.activity_status(),
49
+ )
50
+
15
51
  def invalidate_context_cache(self):
16
52
  self.context_status_cache = None
17
53
 
18
54
  def cache_context_status(self, status):
19
55
  self.context_status_cache = status
56
+ self.status_cache = self.status_from_context_status(status)
57
+ self.publish_status_cache()
20
58
  return status
21
59
 
22
60
  def estimated_context_status(self):
@@ -66,11 +104,11 @@ class StatusManager:
66
104
  sent_session_messages = visible_history_count + len(images)
67
105
  total_session_messages = len(self.agent.session.messages) if self.agent.session else 0
68
106
  persistent_session_messages = sum(
69
- 1 for msg in session_messages
107
+ 1 for msg in all_session_messages
70
108
  if msg.get("turns_left", -1) == -1
71
109
  )
72
110
  temporary_session_messages = sum(
73
- 1 for msg in session_messages
111
+ 1 for msg in all_session_messages
74
112
  if isinstance(msg.get("turns_left", -1), int) and msg.get("turns_left", -1) > 0
75
113
  )
76
114
  wrappers = [msg for msg in all_session_messages if isinstance(msg, ToolResultWrapper)]
@@ -142,44 +180,16 @@ class StatusManager:
142
180
  def get_status(self):
143
181
  if self.status_cache is None and not self.wham_status_loaded:
144
182
  self.refresh_wham_status()
145
- if self.status_cache is not None:
146
- status = self.status_cache.copy()
147
- status.update(
148
- busy=self.agent.mainloop_manager.busy,
149
- current_turn_id=self.agent.mainloop_manager.current_turn_id,
150
- session_id=self.agent.current_session_id,
151
- activity=self.agent.mainloop_manager.activity_status(),
152
- )
153
- return status
154
-
155
- estimated = self.estimated_context_status()
156
- return modict(
157
- context_current_tokens=estimated.used_tokens,
158
- context_limit_tokens=estimated.input_token_limit,
159
- context_percent=estimated.percent,
160
- sent_session_messages=estimated.sent_session_messages,
161
- total_session_messages=estimated.total_session_messages,
162
- sent_session_message_percent=estimated.sent_session_message_percent,
163
- persistent_session_messages=estimated.get("persistent_session_messages", 0),
164
- temporary_session_messages=estimated.get("temporary_session_messages", 0),
165
- active_wrapper_messages=estimated.get("active_wrapper_messages", 0),
166
- vanished_wrapper_messages=estimated.get("vanished_wrapper_messages", 0),
167
- total_wrapper_messages=estimated.get("total_wrapper_messages", 0),
168
- base_context_tokens=estimated.get("base_context_tokens", estimated.used_tokens),
169
- active_wrapper_tokens=estimated.get("active_wrapper_tokens", 0),
170
- vanished_wrapper_tokens=estimated.get("vanished_wrapper_tokens", 0),
171
- compactable_turns=estimated.get("compactable_turns", 0),
172
- compactable_messages=estimated.get("compactable_messages", 0),
173
- compactable_tokens=estimated.get("compactable_tokens", 0),
174
- quota_5h_percent=None,
175
- quota_7d_percent=None,
176
- model=self.agent.config.get("model") or "gpt-5.4",
183
+ if self.status_cache is None:
184
+ self.context_status()
185
+ status = modict(self.status_cache or {})
186
+ status.update(
177
187
  busy=self.agent.mainloop_manager.busy,
178
188
  current_turn_id=self.agent.mainloop_manager.current_turn_id,
179
189
  session_id=self.agent.current_session_id,
180
- source=estimated.get("token_count_source", "estimated"),
181
190
  activity=self.agent.mainloop_manager.activity_status(),
182
191
  )
192
+ return status
183
193
 
184
194
  def refresh_wham_status(self):
185
195
  self.wham_status_loaded = True
@@ -215,10 +225,11 @@ class StatusManager:
215
225
  activity=self.agent.mainloop_manager.activity_status(),
216
226
  )
217
227
  self.status_cache.update(wham)
228
+ self.publish_status_cache()
218
229
  return wham
219
230
 
220
231
  def refresh_estimated_status_cache(self):
221
- estimated = self.estimated_context_status()
232
+ estimated = self.context_status()
222
233
  if self.status_cache is None:
223
234
  return estimated
224
235
  previous = self.status_cache.copy()
@@ -240,7 +251,7 @@ class StatusManager:
240
251
  compactable_turns=estimated.get("compactable_turns", 0),
241
252
  compactable_messages=estimated.get("compactable_messages", 0),
242
253
  compactable_tokens=estimated.get("compactable_tokens", 0),
243
- model=self.agent.config.get("model") or "gpt-5.4",
254
+ model=previous.get("model") or self.agent.config.get("model") or "gpt-5.4",
244
255
  busy=self.agent.mainloop_manager.busy,
245
256
  current_turn_id=self.agent.mainloop_manager.current_turn_id,
246
257
  session_id=self.agent.current_session_id,
@@ -250,6 +261,7 @@ class StatusManager:
250
261
  previous.setdefault("quota_5h_percent", None)
251
262
  previous.setdefault("quota_7d_percent", None)
252
263
  self.status_cache = previous
264
+ self.publish_status_cache()
253
265
  return self.status_cache
254
266
 
255
267
  def cache_api_status(self, usage=None, model=None, quota=None):
@@ -269,7 +281,7 @@ class StatusManager:
269
281
  active_wrapper_messages=context_status.get("active_wrapper_messages", 0),
270
282
  vanished_wrapper_messages=context_status.get("vanished_wrapper_messages", 0),
271
283
  total_wrapper_messages=context_status.get("total_wrapper_messages", 0),
272
- base_context_tokens=context_status.get("base_context_tokens", max(0, input_tokens)),
284
+ base_context_tokens=max(0, input_tokens - context_status.get("active_wrapper_tokens", 0)),
273
285
  active_wrapper_tokens=context_status.get("active_wrapper_tokens", 0),
274
286
  vanished_wrapper_tokens=context_status.get("vanished_wrapper_tokens", 0),
275
287
  compactable_turns=context_status.get("compactable_turns", 0),
@@ -284,6 +296,7 @@ class StatusManager:
284
296
  source="last_api_call",
285
297
  activity=self.agent.mainloop_manager.activity_status(),
286
298
  )
299
+ self.publish_status_cache()
287
300
  return self.status_cache
288
301
 
289
302
  def wham_status(self, quota=None):