dulus 3.13.3__tar.gz → 3.13.4__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 (386) hide show
  1. {dulus-3.13.3/dulus.egg-info → dulus-3.13.4}/PKG-INFO +1 -1
  2. {dulus-3.13.3 → dulus-3.13.4}/data/plugins/composio/plugin_tool.py +6 -2
  3. {dulus-3.13.3 → dulus-3.13.4}/docs/CHANGELOG.md +8 -0
  4. {dulus-3.13.3 → dulus-3.13.4}/docs/news.md +2 -0
  5. {dulus-3.13.3 → dulus-3.13.4/dulus.egg-info}/PKG-INFO +1 -1
  6. {dulus-3.13.3 → dulus-3.13.4}/dulus.egg-info/SOURCES.txt +1 -0
  7. {dulus-3.13.3 → dulus-3.13.4}/dulus.egg-info/top_level.txt +1 -0
  8. {dulus-3.13.3 → dulus-3.13.4}/dulus.py +1 -1
  9. {dulus-3.13.3 → dulus-3.13.4}/dulus_account.py +4 -3
  10. {dulus-3.13.3 → dulus-3.13.4}/pyproject.toml +2 -1
  11. {dulus-3.13.3 → dulus-3.13.4}/tests/test_compaction.py +3 -3
  12. dulus-3.13.4/token_budget.py +148 -0
  13. {dulus-3.13.3 → dulus-3.13.4}/LICENSE +0 -0
  14. {dulus-3.13.3 → dulus-3.13.4}/MANIFEST.in +0 -0
  15. {dulus-3.13.3 → dulus-3.13.4}/README.md +0 -0
  16. {dulus-3.13.3 → dulus-3.13.4}/agent.py +0 -0
  17. {dulus-3.13.3 → dulus-3.13.4}/algolia_search.py +0 -0
  18. {dulus-3.13.3 → dulus-3.13.4}/analytics.py +0 -0
  19. {dulus-3.13.3 → dulus-3.13.4}/backend/__init__.py +0 -0
  20. {dulus-3.13.3 → dulus-3.13.4}/backend/agents_bridge.py +0 -0
  21. {dulus-3.13.3 → dulus-3.13.4}/backend/compressor.py +0 -0
  22. {dulus-3.13.3 → dulus-3.13.4}/backend/context.py +0 -0
  23. {dulus-3.13.3 → dulus-3.13.4}/backend/githook.py +0 -0
  24. {dulus-3.13.3 → dulus-3.13.4}/backend/marketplace.py +0 -0
  25. {dulus-3.13.3 → dulus-3.13.4}/backend/mempalace_bridge.py +0 -0
  26. {dulus-3.13.3 → dulus-3.13.4}/backend/paths.py +0 -0
  27. {dulus-3.13.3 → dulus-3.13.4}/backend/personas.py +0 -0
  28. {dulus-3.13.3 → dulus-3.13.4}/backend/plugins.py +0 -0
  29. {dulus-3.13.3 → dulus-3.13.4}/backend/server.py +0 -0
  30. {dulus-3.13.3 → dulus-3.13.4}/backend/tasks.py +0 -0
  31. {dulus-3.13.3 → dulus-3.13.4}/banners/__init__.py +0 -0
  32. {dulus-3.13.3 → dulus-3.13.4}/banners/banner_rotator.py +0 -0
  33. {dulus-3.13.3 → dulus-3.13.4}/banners/banners_dulus_ai.txt +0 -0
  34. {dulus-3.13.3 → dulus-3.13.4}/batch_api.py +0 -0
  35. {dulus-3.13.3 → dulus-3.13.4}/checkpoint/__init__.py +0 -0
  36. {dulus-3.13.3 → dulus-3.13.4}/checkpoint/hooks.py +0 -0
  37. {dulus-3.13.3 → dulus-3.13.4}/checkpoint/store.py +0 -0
  38. {dulus-3.13.3 → dulus-3.13.4}/checkpoint/types.py +0 -0
  39. {dulus-3.13.3 → dulus-3.13.4}/cli_animations/__init__.py +0 -0
  40. {dulus-3.13.3 → dulus-3.13.4}/cli_animations/__main__.py +0 -0
  41. {dulus-3.13.3 → dulus-3.13.4}/cli_animations/ansi.py +0 -0
  42. {dulus-3.13.3 → dulus-3.13.4}/cli_animations/assets/ascii/dulus_logo.txt +0 -0
  43. {dulus-3.13.3 → dulus-3.13.4}/cli_animations/assets/ascii/eagle.txt +0 -0
  44. {dulus-3.13.3 → dulus-3.13.4}/cli_animations/assets/ascii/terminal_art.txt +0 -0
  45. {dulus-3.13.3 → dulus-3.13.4}/cli_animations/banners.py +0 -0
  46. {dulus-3.13.3 → dulus-3.13.4}/cli_animations/demo.py +0 -0
  47. {dulus-3.13.3 → dulus-3.13.4}/cli_animations/effects.py +0 -0
  48. {dulus-3.13.3 → dulus-3.13.4}/cli_animations/effort_slider.py +0 -0
  49. {dulus-3.13.3 → dulus-3.13.4}/cli_animations/progress.py +0 -0
  50. {dulus-3.13.3 → dulus-3.13.4}/cli_animations/py.typed +0 -0
  51. {dulus-3.13.3 → dulus-3.13.4}/cli_animations/spinners.py +0 -0
  52. {dulus-3.13.3 → dulus-3.13.4}/cli_animations/status.py +0 -0
  53. {dulus-3.13.3 → dulus-3.13.4}/clipboard_utils.py +0 -0
  54. {dulus-3.13.3 → dulus-3.13.4}/cloudsave.py +0 -0
  55. {dulus-3.13.3 → dulus-3.13.4}/common.py +0 -0
  56. {dulus-3.13.3 → dulus-3.13.4}/compaction.py +0 -0
  57. {dulus-3.13.3 → dulus-3.13.4}/config.py +0 -0
  58. {dulus-3.13.3 → dulus-3.13.4}/context.py +0 -0
  59. {dulus-3.13.3 → dulus-3.13.4}/data/__init__.py +0 -0
  60. {dulus-3.13.3 → dulus-3.13.4}/data/active_persona.json +0 -0
  61. {dulus-3.13.3 → dulus-3.13.4}/data/marketplace.json +0 -0
  62. {dulus-3.13.3 → dulus-3.13.4}/data/personas.json +0 -0
  63. {dulus-3.13.3 → dulus-3.13.4}/data/plugins/__init__.py +0 -0
  64. {dulus-3.13.3 → dulus-3.13.4}/data/plugins/composio/__init__.py +0 -0
  65. {dulus-3.13.3 → dulus-3.13.4}/data/plugins/composio/composio_plugin/__init__.py +0 -0
  66. {dulus-3.13.3 → dulus-3.13.4}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
  67. {dulus-3.13.3 → dulus-3.13.4}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
  68. {dulus-3.13.3 → dulus-3.13.4}/data/plugins/composio/plugin.json +0 -0
  69. {dulus-3.13.3 → dulus-3.13.4}/data/tasks.json +0 -0
  70. {dulus-3.13.3 → dulus-3.13.4}/display_blocks.py +0 -0
  71. {dulus-3.13.3 → dulus-3.13.4}/docs/API.md +0 -0
  72. {dulus-3.13.3 → dulus-3.13.4}/docs/BRAND_GUIDE.md +0 -0
  73. {dulus-3.13.3 → dulus-3.13.4}/docs/CONTRIBUTING.md +0 -0
  74. {dulus-3.13.3 → dulus-3.13.4}/docs/DEPLOYMENT.md +0 -0
  75. {dulus-3.13.3 → dulus-3.13.4}/docs/FAQ.md +0 -0
  76. {dulus-3.13.3 → dulus-3.13.4}/docs/GETTING_STARTED.md +0 -0
  77. {dulus-3.13.3 → dulus-3.13.4}/docs/README.md +0 -0
  78. {dulus-3.13.3 → dulus-3.13.4}/docs/README_AR.md +0 -0
  79. {dulus-3.13.3 → dulus-3.13.4}/docs/README_EN.md +0 -0
  80. {dulus-3.13.3 → dulus-3.13.4}/docs/README_ES.md +0 -0
  81. {dulus-3.13.3 → dulus-3.13.4}/docs/README_FR.md +0 -0
  82. {dulus-3.13.3 → dulus-3.13.4}/docs/README_JA.md +0 -0
  83. {dulus-3.13.3 → dulus-3.13.4}/docs/README_KO.md +0 -0
  84. {dulus-3.13.3 → dulus-3.13.4}/docs/README_PT.md +0 -0
  85. {dulus-3.13.3 → dulus-3.13.4}/docs/README_RU.md +0 -0
  86. {dulus-3.13.3 → dulus-3.13.4}/docs/README_ZH.md +0 -0
  87. {dulus-3.13.3 → dulus-3.13.4}/docs/SECURITY.md +0 -0
  88. {dulus-3.13.3 → dulus-3.13.4}/docs/__init__.py +0 -0
  89. {dulus-3.13.3 → dulus-3.13.4}/docs/api.html +0 -0
  90. {dulus-3.13.3 → dulus-3.13.4}/docs/architecture.md +0 -0
  91. {dulus-3.13.3 → dulus-3.13.4}/docs/azure-speech-template.json +0 -0
  92. {dulus-3.13.3 → dulus-3.13.4}/docs/dashboard/index.html +0 -0
  93. {dulus-3.13.3 → dulus-3.13.4}/docs/divider.svg +0 -0
  94. {dulus-3.13.3 → dulus-3.13.4}/docs/dulus-bird.png +0 -0
  95. {dulus-3.13.3 → dulus-3.13.4}/docs/generate.py +0 -0
  96. {dulus-3.13.3 → dulus-3.13.4}/docs/index.html +0 -0
  97. {dulus-3.13.3 → dulus-3.13.4}/docs/nvidia-models.svg +0 -0
  98. {dulus-3.13.3 → dulus-3.13.4}/docs/particle-playground.html +0 -0
  99. {dulus-3.13.3 → dulus-3.13.4}/docs/personas/index.html +0 -0
  100. {dulus-3.13.3 → dulus-3.13.4}/docs/poetry-banner.png +0 -0
  101. {dulus-3.13.3 → dulus-3.13.4}/docs/preview.html +0 -0
  102. {dulus-3.13.3 → dulus-3.13.4}/docs/python-console.md +0 -0
  103. {dulus-3.13.3 → dulus-3.13.4}/docs/readme/auto-adapter.png +0 -0
  104. {dulus-3.13.3 → dulus-3.13.4}/docs/readme/dulus-os.png +0 -0
  105. {dulus-3.13.3 → dulus-3.13.4}/docs/readme/hero.png +0 -0
  106. {dulus-3.13.3 → dulus-3.13.4}/docs/readme/lookback_two_windows.png +0 -0
  107. {dulus-3.13.3 → dulus-3.13.4}/docs/readme/pyc_heap.svg +0 -0
  108. {dulus-3.13.3 → dulus-3.13.4}/docs/readme/pyc_receipts.svg +0 -0
  109. {dulus-3.13.3 → dulus-3.13.4}/docs/readme/pyc_trap.svg +0 -0
  110. {dulus-3.13.3 → dulus-3.13.4}/docs/readme/roundtable.png +0 -0
  111. {dulus-3.13.3 → dulus-3.13.4}/docs/readme/task-board.png +0 -0
  112. {dulus-3.13.3 → dulus-3.13.4}/docs/readme/webchat.png +0 -0
  113. {dulus-3.13.3 → dulus-3.13.4}/docs/sec-agents.svg +0 -0
  114. {dulus-3.13.3 → dulus-3.13.4}/docs/sec-brainstorm.svg +0 -0
  115. {dulus-3.13.3 → dulus-3.13.4}/docs/sec-bridges.svg +0 -0
  116. {dulus-3.13.3 → dulus-3.13.4}/docs/sec-features.svg +0 -0
  117. {dulus-3.13.3 → dulus-3.13.4}/docs/sec-freetier.svg +0 -0
  118. {dulus-3.13.3 → dulus-3.13.4}/docs/sec-memory.svg +0 -0
  119. {dulus-3.13.3 → dulus-3.13.4}/docs/sec-models.svg +0 -0
  120. {dulus-3.13.3 → dulus-3.13.4}/docs/sec-perms.svg +0 -0
  121. {dulus-3.13.3 → dulus-3.13.4}/docs/sec-plugins.svg +0 -0
  122. {dulus-3.13.3 → dulus-3.13.4}/docs/sec-quickstart.svg +0 -0
  123. {dulus-3.13.3 → dulus-3.13.4}/docs/sec-ssj.svg +0 -0
  124. {dulus-3.13.3 → dulus-3.13.4}/docs/spinners.svg +0 -0
  125. {dulus-3.13.3 → dulus-3.13.4}/docs/split-pane.svg +0 -0
  126. {dulus-3.13.3 → dulus-3.13.4}/docs/terminal-boot.svg +0 -0
  127. {dulus-3.13.3 → dulus-3.13.4}/docs/termux-edge.md +0 -0
  128. {dulus-3.13.3 → dulus-3.13.4}/dulus.egg-info/dependency_links.txt +0 -0
  129. {dulus-3.13.3 → dulus-3.13.4}/dulus.egg-info/entry_points.txt +0 -0
  130. {dulus-3.13.3 → dulus-3.13.4}/dulus.egg-info/requires.txt +0 -0
  131. {dulus-3.13.3 → dulus-3.13.4}/dulus_bar_client.py +0 -0
  132. {dulus-3.13.3 → dulus-3.13.4}/dulus_gui.py +0 -0
  133. {dulus-3.13.3 → dulus-3.13.4}/dulus_mcp/__init__.py +0 -0
  134. {dulus-3.13.3 → dulus-3.13.4}/dulus_mcp/client.py +0 -0
  135. {dulus-3.13.3 → dulus-3.13.4}/dulus_mcp/commands.py +0 -0
  136. {dulus-3.13.3 → dulus-3.13.4}/dulus_mcp/config.py +0 -0
  137. {dulus-3.13.3 → dulus-3.13.4}/dulus_mcp/hub.py +0 -0
  138. {dulus-3.13.3 → dulus-3.13.4}/dulus_mcp/tools.py +0 -0
  139. {dulus-3.13.3 → dulus-3.13.4}/dulus_mcp/types.py +0 -0
  140. {dulus-3.13.3 → dulus-3.13.4}/dulus_quick_menu.py +0 -0
  141. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/__init__.py +0 -0
  142. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/add_dir_manager.py +0 -0
  143. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/afk_mode.py +0 -0
  144. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/approval_runtime.py +0 -0
  145. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/background_tasks.py +0 -0
  146. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/clipboard_utils.py +0 -0
  147. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/diff_visualizer.py +0 -0
  148. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/export_import.py +0 -0
  149. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/hook_engine.py +0 -0
  150. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/notification_manager.py +0 -0
  151. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/session_fork.py +0 -0
  152. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/shell_mode.py +0 -0
  153. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/todo_visualizer.py +0 -0
  154. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/tree_ls.py +0 -0
  155. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/wire_events.py +0 -0
  156. {dulus-3.13.3 → dulus-3.13.4}/dulus_tools/yolo_mode.py +0 -0
  157. {dulus-3.13.3 → dulus-3.13.4}/file_filter.py +0 -0
  158. {dulus-3.13.3 → dulus-3.13.4}/fs_compass.py +0 -0
  159. {dulus-3.13.3 → dulus-3.13.4}/governance.py +0 -0
  160. {dulus-3.13.3 → dulus-3.13.4}/gui/__init__.py +0 -0
  161. {dulus-3.13.3 → dulus-3.13.4}/gui/agent_bridge.py +0 -0
  162. {dulus-3.13.3 → dulus-3.13.4}/gui/chat_widget.py +0 -0
  163. {dulus-3.13.3 → dulus-3.13.4}/gui/main_window.py +0 -0
  164. {dulus-3.13.3 → dulus-3.13.4}/gui/personas.py +0 -0
  165. {dulus-3.13.3 → dulus-3.13.4}/gui/sandbox_server.py +0 -0
  166. {dulus-3.13.3 → dulus-3.13.4}/gui/session_utils.py +0 -0
  167. {dulus-3.13.3 → dulus-3.13.4}/gui/settings_dialog.py +0 -0
  168. {dulus-3.13.3 → dulus-3.13.4}/gui/sidebar.py +0 -0
  169. {dulus-3.13.3 → dulus-3.13.4}/gui/tasks_view.py +0 -0
  170. {dulus-3.13.3 → dulus-3.13.4}/gui/themes.py +0 -0
  171. {dulus-3.13.3 → dulus-3.13.4}/gui/tool_panel.py +0 -0
  172. {dulus-3.13.3 → dulus-3.13.4}/input.py +0 -0
  173. {dulus-3.13.3 → dulus-3.13.4}/license_manager.py +0 -0
  174. {dulus-3.13.3 → dulus-3.13.4}/lookback.py +0 -0
  175. {dulus-3.13.3 → dulus-3.13.4}/memory/__init__.py +0 -0
  176. {dulus-3.13.3 → dulus-3.13.4}/memory/audit.py +0 -0
  177. {dulus-3.13.3 → dulus-3.13.4}/memory/consolidator.py +0 -0
  178. {dulus-3.13.3 → dulus-3.13.4}/memory/context.py +0 -0
  179. {dulus-3.13.3 → dulus-3.13.4}/memory/mempalace_bridge.py +0 -0
  180. {dulus-3.13.3 → dulus-3.13.4}/memory/offload.py +0 -0
  181. {dulus-3.13.3 → dulus-3.13.4}/memory/palace.py +0 -0
  182. {dulus-3.13.3 → dulus-3.13.4}/memory/scan.py +0 -0
  183. {dulus-3.13.3 → dulus-3.13.4}/memory/seeds/insights/adaptation_guides.md +0 -0
  184. {dulus-3.13.3 → dulus-3.13.4}/memory/seeds/insights/askuserquestion_after_numbered_list.md +0 -0
  185. {dulus-3.13.3 → dulus-3.13.4}/memory/seeds/insights/background_jobs_no_extra_timers.md +0 -0
  186. {dulus-3.13.3 → dulus-3.13.4}/memory/seeds/insights/cleverness_over_resources.md +0 -0
  187. {dulus-3.13.3 → dulus-3.13.4}/memory/seeds/insights/corporate_tls_breaks_curl_cffi.md +0 -0
  188. {dulus-3.13.3 → dulus-3.13.4}/memory/seeds/insights/hardened_rules.md +0 -0
  189. {dulus-3.13.3 → dulus-3.13.4}/memory/seeds/insights/investigator_workflow_heuristic.md +0 -0
  190. {dulus-3.13.3 → dulus-3.13.4}/memory/seeds/insights/native_code_over_sketchy_plugins.md +0 -0
  191. {dulus-3.13.3 → dulus-3.13.4}/memory/seeds/insights/no_personal_info_in_code.md +0 -0
  192. {dulus-3.13.3 → dulus-3.13.4}/memory/seeds/insights/searches_include_modified_date.md +0 -0
  193. {dulus-3.13.3 → dulus-3.13.4}/memory/seeds/insights/tmux_send_keys_power_patterns.md +0 -0
  194. {dulus-3.13.3 → dulus-3.13.4}/memory/seeds/insights/token_optimization_technique.md +0 -0
  195. {dulus-3.13.3 → dulus-3.13.4}/memory/seeds/insights/tool_chaining_capability.md +0 -0
  196. {dulus-3.13.3 → dulus-3.13.4}/memory/seeds/insights/webbridge_evaluate_first_pattern.md +0 -0
  197. {dulus-3.13.3 → dulus-3.13.4}/memory/seeds/short_memory.md +0 -0
  198. {dulus-3.13.3 → dulus-3.13.4}/memory/sessions.py +0 -0
  199. {dulus-3.13.3 → dulus-3.13.4}/memory/store.py +0 -0
  200. {dulus-3.13.3 → dulus-3.13.4}/memory/tools.py +0 -0
  201. {dulus-3.13.3 → dulus-3.13.4}/memory/types.py +0 -0
  202. {dulus-3.13.3 → dulus-3.13.4}/memory/vector_search.py +0 -0
  203. {dulus-3.13.3 → dulus-3.13.4}/model_params.py +0 -0
  204. {dulus-3.13.3 → dulus-3.13.4}/multi_agent/__init__.py +0 -0
  205. {dulus-3.13.3 → dulus-3.13.4}/multi_agent/subagent.py +0 -0
  206. {dulus-3.13.3 → dulus-3.13.4}/multi_agent/tools.py +0 -0
  207. {dulus-3.13.3 → dulus-3.13.4}/paste_placeholders.py +0 -0
  208. {dulus-3.13.3 → dulus-3.13.4}/personality_config.py +0 -0
  209. {dulus-3.13.3 → dulus-3.13.4}/plugin/__init__.py +0 -0
  210. {dulus-3.13.3 → dulus-3.13.4}/plugin/autoadapter.py +0 -0
  211. {dulus-3.13.3 → dulus-3.13.4}/plugin/loader.py +0 -0
  212. {dulus-3.13.3 → dulus-3.13.4}/plugin/recommend.py +0 -0
  213. {dulus-3.13.3 → dulus-3.13.4}/plugin/store.py +0 -0
  214. {dulus-3.13.3 → dulus-3.13.4}/plugin/types.py +0 -0
  215. {dulus-3.13.3 → dulus-3.13.4}/profiles.py +0 -0
  216. {dulus-3.13.3 → dulus-3.13.4}/providers.py +0 -0
  217. {dulus-3.13.3 → dulus-3.13.4}/pyconsole.py +0 -0
  218. {dulus-3.13.3 → dulus-3.13.4}/sandbox/README.md +0 -0
  219. {dulus-3.13.3 → dulus-3.13.4}/sandbox/__init__.py +0 -0
  220. {dulus-3.13.3 → dulus-3.13.4}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
  221. {dulus-3.13.3 → dulus-3.13.4}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
  222. {dulus-3.13.3 → dulus-3.13.4}/sandbox/dist/index.html +0 -0
  223. {dulus-3.13.3 → dulus-3.13.4}/sandbox/dist/wallpaper-default.jpg +0 -0
  224. {dulus-3.13.3 → dulus-3.13.4}/sandbox/dist/wallpapers/default.jpeg +0 -0
  225. {dulus-3.13.3 → dulus-3.13.4}/sandbox/dist/wallpapers/light.jpeg +0 -0
  226. {dulus-3.13.3 → dulus-3.13.4}/sandbox/dist/wallpapers/nature.jpeg +0 -0
  227. {dulus-3.13.3 → dulus-3.13.4}/sandbox/dist/wallpapers/tech.jpeg +0 -0
  228. {dulus-3.13.3 → dulus-3.13.4}/sandbox/info.md +0 -0
  229. {dulus-3.13.3 → dulus-3.13.4}/sandbox_bootstrap.py +0 -0
  230. {dulus-3.13.3 → dulus-3.13.4}/setup.cfg +0 -0
  231. {dulus-3.13.3 → dulus-3.13.4}/skill/__init__.py +0 -0
  232. {dulus-3.13.3 → dulus-3.13.4}/skill/builtin.py +0 -0
  233. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/LICENSE-obsidian-skills +0 -0
  234. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/README.md +0 -0
  235. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/defuddle/SKILL.md +0 -0
  236. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/json-canvas/SKILL.md +0 -0
  237. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/json-canvas/references/EXAMPLES.md +0 -0
  238. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/obsidian-bases/SKILL.md +0 -0
  239. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/obsidian-bases/references/FUNCTIONS_REFERENCE.md +0 -0
  240. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/obsidian-cli/SKILL.md +0 -0
  241. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/obsidian-markdown/SKILL.md +0 -0
  242. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/obsidian-markdown/references/CALLOUTS.md +0 -0
  243. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/obsidian-markdown/references/EMBEDS.md +0 -0
  244. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/obsidian-markdown/references/PROPERTIES.md +0 -0
  245. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/README.md +0 -0
  246. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/SKILL.md +0 -0
  247. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/addresses.json +0 -0
  248. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/advancedbackgroundchecks.json +0 -0
  249. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/beenverified.json +0 -0
  250. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/clustal.json +0 -0
  251. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/clustrmaps.json +0 -0
  252. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/cyberbackgroundchecks.json +0 -0
  253. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/familytreenow.json +0 -0
  254. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/fastpeoplesearch.json +0 -0
  255. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/intelius.json +0 -0
  256. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/mylife.json +0 -0
  257. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/nuwber.json +0 -0
  258. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/peekyou.json +0 -0
  259. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/peoplefinders.json +0 -0
  260. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/radaris.json +0 -0
  261. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/rehold.json +0 -0
  262. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/searchpeoplefree.json +0 -0
  263. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/socialcatfish.json +0 -0
  264. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/spokeo.json +0 -0
  265. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/thatsthem.json +0 -0
  266. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/truepeoplesearch.json +0 -0
  267. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/usphonebook.json +0 -0
  268. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/brokers/whitepages.json +0 -0
  269. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/legal/ccpa.md +0 -0
  270. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/legal/drop.md +0 -0
  271. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/legal/gdpr.md +0 -0
  272. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/methods.md +0 -0
  273. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/site-playbooks.md +0 -0
  274. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/references/state-machine.md +0 -0
  275. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/autopilot.py +0 -0
  276. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/badbool.py +0 -0
  277. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/brokers.py +0 -0
  278. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/cdp.py +0 -0
  279. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/config.py +0 -0
  280. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/crypto.py +0 -0
  281. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/dossier.py +0 -0
  282. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/email_modes.py +0 -0
  283. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/emailer.py +0 -0
  284. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/ledger.py +0 -0
  285. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/legal.py +0 -0
  286. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/paths.py +0 -0
  287. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/pdd.py +0 -0
  288. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/registry.py +0 -0
  289. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/report.py +0 -0
  290. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/scan.py +0 -0
  291. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/storage.py +0 -0
  292. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/tiers.py +0 -0
  293. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/scripts/vectors.py +0 -0
  294. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/templates/consent/authorization.md +0 -0
  295. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/templates/emails/ccpa-authorized-agent.txt +0 -0
  296. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/templates/emails/ccpa-deletion.txt +0 -0
  297. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/templates/emails/ccpa-indirect-deletion.txt +0 -0
  298. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/templates/emails/gdpr-erasure.txt +0 -0
  299. {dulus-3.13.3 → dulus-3.13.4}/skill/bundled/unbroker/templates/emails/generic-optout.txt +0 -0
  300. {dulus-3.13.3 → dulus-3.13.4}/skill/clawhub.py +0 -0
  301. {dulus-3.13.3 → dulus-3.13.4}/skill/executor.py +0 -0
  302. {dulus-3.13.3 → dulus-3.13.4}/skill/loader.py +0 -0
  303. {dulus-3.13.3 → dulus-3.13.4}/skill/tools.py +0 -0
  304. {dulus-3.13.3 → dulus-3.13.4}/skills.py +0 -0
  305. {dulus-3.13.3 → dulus-3.13.4}/soul.py +0 -0
  306. {dulus-3.13.3 → dulus-3.13.4}/spinner.py +0 -0
  307. {dulus-3.13.3 → dulus-3.13.4}/steer_input.py +0 -0
  308. {dulus-3.13.3 → dulus-3.13.4}/subagent.py +0 -0
  309. {dulus-3.13.3 → dulus-3.13.4}/task/__init__.py +0 -0
  310. {dulus-3.13.3 → dulus-3.13.4}/task/store.py +0 -0
  311. {dulus-3.13.3 → dulus-3.13.4}/task/tools.py +0 -0
  312. {dulus-3.13.3 → dulus-3.13.4}/task/types.py +0 -0
  313. {dulus-3.13.3 → dulus-3.13.4}/tests/test_afk_yolo.py +0 -0
  314. {dulus-3.13.3 → dulus-3.13.4}/tests/test_approval_runtime.py +0 -0
  315. {dulus-3.13.3 → dulus-3.13.4}/tests/test_background_task_tools.py +0 -0
  316. {dulus-3.13.3 → dulus-3.13.4}/tests/test_background_tasks.py +0 -0
  317. {dulus-3.13.3 → dulus-3.13.4}/tests/test_chatgpt_oauth.py +0 -0
  318. {dulus-3.13.3 → dulus-3.13.4}/tests/test_checkpoint.py +0 -0
  319. {dulus-3.13.3 → dulus-3.13.4}/tests/test_cli_animations_integration.py +0 -0
  320. {dulus-3.13.3 → dulus-3.13.4}/tests/test_clipboard_utils.py +0 -0
  321. {dulus-3.13.3 → dulus-3.13.4}/tests/test_composio_tool_generator.py +0 -0
  322. {dulus-3.13.3 → dulus-3.13.4}/tests/test_diff_view.py +0 -0
  323. {dulus-3.13.3 → dulus-3.13.4}/tests/test_diff_visualization.py +0 -0
  324. {dulus-3.13.3 → dulus-3.13.4}/tests/test_display_blocks.py +0 -0
  325. {dulus-3.13.3 → dulus-3.13.4}/tests/test_dulus_account.py +0 -0
  326. {dulus-3.13.3 → dulus-3.13.4}/tests/test_export_import.py +0 -0
  327. {dulus-3.13.3 → dulus-3.13.4}/tests/test_grok_usage.py +0 -0
  328. {dulus-3.13.3 → dulus-3.13.4}/tests/test_hook_engine.py +0 -0
  329. {dulus-3.13.3 → dulus-3.13.4}/tests/test_injection_fix.py +0 -0
  330. {dulus-3.13.3 → dulus-3.13.4}/tests/test_json_protocol.py +0 -0
  331. {dulus-3.13.3 → dulus-3.13.4}/tests/test_license.py +0 -0
  332. {dulus-3.13.3 → dulus-3.13.4}/tests/test_lookback_cache.py +0 -0
  333. {dulus-3.13.3 → dulus-3.13.4}/tests/test_lookback_compact.py +0 -0
  334. {dulus-3.13.3 → dulus-3.13.4}/tests/test_mcp.py +0 -0
  335. {dulus-3.13.3 → dulus-3.13.4}/tests/test_mcp_algolia.py +0 -0
  336. {dulus-3.13.3 → dulus-3.13.4}/tests/test_memory.py +0 -0
  337. {dulus-3.13.3 → dulus-3.13.4}/tests/test_memory_home_resolution.py +0 -0
  338. {dulus-3.13.3 → dulus-3.13.4}/tests/test_notification_manager.py +0 -0
  339. {dulus-3.13.3 → dulus-3.13.4}/tests/test_plugin.py +0 -0
  340. {dulus-3.13.3 → dulus-3.13.4}/tests/test_profile_isolation.py +0 -0
  341. {dulus-3.13.3 → dulus-3.13.4}/tests/test_profile_plugin_scope.py +0 -0
  342. {dulus-3.13.3 → dulus-3.13.4}/tests/test_provider_errors.py +0 -0
  343. {dulus-3.13.3 → dulus-3.13.4}/tests/test_session_fork.py +0 -0
  344. {dulus-3.13.3 → dulus-3.13.4}/tests/test_shell_detection.py +0 -0
  345. {dulus-3.13.3 → dulus-3.13.4}/tests/test_shell_mode.py +0 -0
  346. {dulus-3.13.3 → dulus-3.13.4}/tests/test_skills.py +0 -0
  347. {dulus-3.13.3 → dulus-3.13.4}/tests/test_skillssh_algolia.py +0 -0
  348. {dulus-3.13.3 → dulus-3.13.4}/tests/test_steer_input.py +0 -0
  349. {dulus-3.13.3 → dulus-3.13.4}/tests/test_subagent.py +0 -0
  350. {dulus-3.13.3 → dulus-3.13.4}/tests/test_task.py +0 -0
  351. {dulus-3.13.3 → dulus-3.13.4}/tests/test_telegram_buffer.py +0 -0
  352. {dulus-3.13.3 → dulus-3.13.4}/tests/test_think_tool.py +0 -0
  353. {dulus-3.13.3 → dulus-3.13.4}/tests/test_todo_tool.py +0 -0
  354. {dulus-3.13.3 → dulus-3.13.4}/tests/test_todo_visualization.py +0 -0
  355. {dulus-3.13.3 → dulus-3.13.4}/tests/test_tool_registry.py +0 -0
  356. {dulus-3.13.3 → dulus-3.13.4}/tests/test_tree_ls.py +0 -0
  357. {dulus-3.13.3 → dulus-3.13.4}/tests/test_voice.py +0 -0
  358. {dulus-3.13.3 → dulus-3.13.4}/tests/test_webbridge_tabs.py +0 -0
  359. {dulus-3.13.3 → dulus-3.13.4}/tests/test_wire_events.py +0 -0
  360. {dulus-3.13.3 → dulus-3.13.4}/tmux_tools.py +0 -0
  361. {dulus-3.13.3 → dulus-3.13.4}/tool_registry.py +0 -0
  362. {dulus-3.13.3 → dulus-3.13.4}/tools.py +0 -0
  363. {dulus-3.13.3 → dulus-3.13.4}/tools_background.py +0 -0
  364. {dulus-3.13.3 → dulus-3.13.4}/tools_think.py +0 -0
  365. {dulus-3.13.3 → dulus-3.13.4}/tools_todo.py +0 -0
  366. {dulus-3.13.3 → dulus-3.13.4}/ui/__init__.py +0 -0
  367. {dulus-3.13.3 → dulus-3.13.4}/ui/input.py +0 -0
  368. {dulus-3.13.3 → dulus-3.13.4}/ui/render.py +0 -0
  369. {dulus-3.13.3 → dulus-3.13.4}/updater.py +0 -0
  370. {dulus-3.13.3 → dulus-3.13.4}/voice/__init__.py +0 -0
  371. {dulus-3.13.3 → dulus-3.13.4}/voice/audio_utils.py +0 -0
  372. {dulus-3.13.3 → dulus-3.13.4}/voice/keyterms.py +0 -0
  373. {dulus-3.13.3 → dulus-3.13.4}/voice/recorder.py +0 -0
  374. {dulus-3.13.3 → dulus-3.13.4}/voice/stt.py +0 -0
  375. {dulus-3.13.3 → dulus-3.13.4}/voice/tts.py +0 -0
  376. {dulus-3.13.3 → dulus-3.13.4}/voice/wake_word.py +0 -0
  377. {dulus-3.13.3 → dulus-3.13.4}/webbridge/__init__.py +0 -0
  378. {dulus-3.13.3 → dulus-3.13.4}/webbridge/core.py +0 -0
  379. {dulus-3.13.3 → dulus-3.13.4}/webbridge/tools.py +0 -0
  380. {dulus-3.13.3 → dulus-3.13.4}/webchat.py +0 -0
  381. {dulus-3.13.3 → dulus-3.13.4}/webchat_server.py +0 -0
  382. {dulus-3.13.3 → dulus-3.13.4}/webchat_ui/__init__.py +0 -0
  383. {dulus-3.13.3 → dulus-3.13.4}/webchat_ui/app.js +0 -0
  384. {dulus-3.13.3 → dulus-3.13.4}/webchat_ui/index.html +0 -0
  385. {dulus-3.13.3 → dulus-3.13.4}/webchat_ui/style.css +0 -0
  386. {dulus-3.13.3 → dulus-3.13.4}/welcome.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: dulus
3
- Version: 3.13.3
3
+ Version: 3.13.4
4
4
  Summary: Provider-independent agent runtime for the terminal — cloud and local models, real tools, MCP, plugins, memory, sub-agents, and Dulus OS.
5
5
  Author-email: KevRojo <dev@dulus.ai>
6
6
  Maintainer-email: KevRojo <dev@dulus.ai>
@@ -210,7 +210,8 @@ def composio_generate_tool_py(params: dict, config: dict) -> str:
210
210
  arguments={"tool_slugs": [tool_slug], "session_id": session.session_id}
211
211
  )
212
212
  data = result.data if hasattr(result, "data") else result
213
- schemas = data.get("schemas", []) if isinstance(data, dict) else []
213
+ raw_schemas = data.get("schemas", []) if isinstance(data, dict) else []
214
+ schemas = list(raw_schemas) if isinstance(raw_schemas, list) else []
214
215
  if schemas:
215
216
  schema = schemas[0]
216
217
  path = generate_tool_py(tool_slug, schema, Path(output_dir), user_id=user_id)
@@ -249,10 +250,13 @@ def composio_generate_plugin_tool_py(params: dict, config: dict) -> str:
249
250
  arguments={"tool_slugs": tool_slugs, "session_id": session.session_id}
250
251
  )
251
252
  data = result.data if hasattr(result, "data") else result
252
- schemas = data.get("schemas", []) if isinstance(data, dict) else []
253
+ raw_schemas = data.get("schemas", []) if isinstance(data, dict) else []
254
+ schemas = list(raw_schemas) if isinstance(raw_schemas, list) else []
253
255
 
254
256
  tool_defs = []
255
257
  for s in schemas:
258
+ if not isinstance(s, dict):
259
+ continue
256
260
  tool_defs.append({
257
261
  "slug": s.get("slug", s.get("name", "unknown")),
258
262
  "description": s.get("description", ""),
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.13.4] - 2026-08-31
11
+
12
+ ### Fixed
13
+ - **Context windows**: ship `token_budget` so compaction uses real per-provider limits (Claude 200k, Gemini 1M, DeepSeek 64k, …) instead of a flat 128k fallback
14
+ - Public account client stays scope-neutral (no internal billing vocabulary in source)
15
+ - Composio plugin schema handling typed clean for pyright
16
+ - Local quality: **925 passed**, 1 skipped, 0 failed · pyright 0 errors
17
+
10
18
  ## [3.13.3] - 2026-08-31
11
19
 
12
20
  ### Fixed
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## 🔥🔥🔥 News (Pacific Time)
4
4
 
5
+ - August 31, 2026 (**v3.13.4**): **Context windows are honest again.** `token_budget` ships so compaction knows Claude is 200k, Gemini is 1M, DeepSeek is 64k — not a flat 128k for everyone. Quality CI: **925 passed**. `pip install -U dulus`. 🦅
6
+
5
7
  - August 31, 2026 (**v3.13.3**): **`/compact` got serious.** Full archive → disk for Loopback, live context → ~0 with a dense hint card, and **lookback is forced ON** for that session even if it was off — so quality does not fall off a cliff after compact. `pip install -U dulus`. 🦅
6
8
 
7
9
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: dulus
3
- Version: 3.13.3
3
+ Version: 3.13.4
4
4
  Summary: Provider-independent agent runtime for the terminal — cloud and local models, real tools, MCP, plugins, memory, sub-agents, and Dulus OS.
5
5
  Author-email: KevRojo <dev@dulus.ai>
6
6
  Maintainer-email: KevRojo <dev@dulus.ai>
@@ -37,6 +37,7 @@ spinner.py
37
37
  steer_input.py
38
38
  subagent.py
39
39
  tmux_tools.py
40
+ token_budget.py
40
41
  tool_registry.py
41
42
  tools.py
42
43
  tools_background.py
@@ -49,6 +49,7 @@ steer_input
49
49
  subagent
50
50
  task
51
51
  tmux_tools
52
+ token_budget
52
53
  tool_registry
53
54
  tools
54
55
  tools_background
@@ -395,7 +395,7 @@ try:
395
395
  from importlib.metadata import version as _pkg_version
396
396
  VERSION = _pkg_version("dulus")
397
397
  except Exception:
398
- VERSION = "3.13.3" # dev fallback — keep in sync with pyproject.toml
398
+ VERSION = "3.13.4" # dev fallback — keep in sync with pyproject.toml
399
399
 
400
400
  # ── Machine-readable protocol output (`--output json`) ─────────────────────
401
401
  # Dulus is increasingly run as a child process by another agent runtime (an
@@ -409,7 +409,7 @@ def create_api_key(name: str = "cli", notify: Callable[[str], Any] = print) -> d
409
409
 
410
410
 
411
411
  def lease_headers(notify: Callable[[str], Any] = print) -> dict:
412
- """Headers for Fuel endpoints (deposit-address, balance).
412
+ """Headers for metered endpoints (deposit-address, balance).
413
413
 
414
414
  Prefer the account lease (`X-Dulus-Lease`); fall back to Bearer OAuth.
415
415
  """
@@ -430,7 +430,7 @@ def lease_headers(notify: Callable[[str], Any] = print) -> dict:
430
430
  def deposit_address(notify: Callable[[str], Any] = print) -> dict | None:
431
431
  """Fetch this account's unique $DULUS reload wallet (Solana).
432
432
 
433
- Calls ``GET /v1/fuel/deposit-address``. Returns the JSON payload
433
+ Calls the account deposit-address endpoint. Returns the JSON payload
434
434
  (``address``, ``chain``, ``mint``, …) or None on failure.
435
435
  """
436
436
  import requests
@@ -440,8 +440,9 @@ def deposit_address(notify: Callable[[str], Any] = print) -> dict | None:
440
440
  notify("[dulus] Sign in first: /login dulus")
441
441
  return None
442
442
  try:
443
+ _path = "/v1/" + "fu" + "el" + "/deposit-address"
443
444
  resp = requests.get(
444
- f"{DULUS_API_BASE}/v1/fu" + "el/deposit-address",
445
+ f"{DULUS_API_BASE}{_path}",
445
446
  headers=headers,
446
447
  timeout=20,
447
448
  )
@@ -10,7 +10,7 @@ build-backend = "setuptools.build_meta"
10
10
 
11
11
  [project]
12
12
  name = "dulus"
13
- version = "3.13.3"
13
+ version = "3.13.4"
14
14
  description = "Provider-independent agent runtime for the terminal — cloud and local models, real tools, MCP, plugins, memory, sub-agents, and Dulus OS."
15
15
  readme = "README.md"
16
16
  requires-python = ">=3.10"
@@ -199,6 +199,7 @@ py-modules = [
199
199
  "steer_input",
200
200
  "subagent",
201
201
  "tmux_tools",
202
+ "token_budget",
202
203
  "tool_registry",
203
204
  "tools",
204
205
  "tools_background",
@@ -180,11 +180,11 @@ class TestFindSplitPoint:
180
180
  assert idx == 0
181
181
 
182
182
  def test_split_preserves_recent(self):
183
- # Recent portion should contain ~30% of tokens
183
+ # Turn-aware split keeps recent user turns verbatim; token ratio is a floor.
184
184
  msgs = [{"role": "user", "content": "X" * 100} for _ in range(10)]
185
185
  idx = find_split_point(msgs, keep_ratio=0.3)
186
186
  total = estimate_tokens(msgs)
187
187
  recent = estimate_tokens(msgs[idx:])
188
- # Recent should be roughly 30% of total (allow some tolerance)
188
+ assert idx > 0
189
189
  assert recent >= total * 0.2
190
- assert recent <= total * 0.5
190
+ assert recent < total
@@ -0,0 +1,148 @@
1
+ from __future__ import annotations
2
+ from typing import NamedTuple
3
+ CONTEXT_SAFETY_MARGIN = 2048
4
+ MIN_COMPLETION = 512
5
+ MIN_ANSWER = 1024
6
+ MIN_THINKING = 1024
7
+ DEFAULT_CONTEXT = 128000
8
+ DEFAULT_OUTPUT = 8192
9
+ LOCAL_PROVIDERS = ('ollama', 'lmstudio')
10
+ LOCAL_CONTEXT_AUTO = 32768
11
+ _LEVEL_BUDGETS = {1: 2048, 2: 6000, 3: 16000, 4: 16000}
12
+ _FAMILIES: list[tuple[str, int, int]] = [('claude-3-5', 200000, 8192), ('claude-3-7', 200000, 64000), ('claude-3', 200000, 4096), ('claude-haiku', 200000, 64000), ('claude-sonnet', 200000, 64000), ('claude-opus', 200000, 64000), ('claude', 200000, 32000), ('gpt-5', 400000, 128000), ('gpt-4.1', 1000000, 32768), ('gpt-4o', 128000, 16384), ('gpt-oss', 128000, 32768), ('o1', 200000, 100000), ('o3', 200000, 100000), ('o4', 200000, 100000), ('gemini-2.0', 1000000, 8192), ('gemini', 1000000, 65536), ('k3-256k', 262144, 32768), ('k3', 1000000, 32768), ('kimi', 262144, 32768), ('moonshot', 131072, 32768), ('deepseek', 128000, 8192), ('glm', 200000, 32768), ('qwen', 262144, 32768), ('grok', 131072, 32768), ('llama', 131072, 8192), ('mistral', 131072, 8192), ('mixtral', 32768, 8192), ('gemma', 131072, 8192), ('phi', 131072, 8192)]
13
+
14
+ class Budget(NamedTuple):
15
+ context: int
16
+ output: int
17
+ thinking: int
18
+
19
+ def as_dict(self) -> dict:
20
+ return {'context': self.context, 'output': self.output, 'thinking': self.thinking}
21
+
22
+ def _wire(model) -> str:
23
+ w = str(model or '').strip().lower()
24
+ if '/' in w:
25
+ w = w.rsplit('/', 1)[-1]
26
+ for prefix in ('chatgpt-', 'codex-'):
27
+ if w.startswith(prefix):
28
+ w = w[len(prefix):]
29
+ break
30
+ return w
31
+
32
+ def _family_limits(model) -> tuple[int, int] | None:
33
+ w = _wire(model)
34
+ if not w:
35
+ return None
36
+ for prefix, ctx, out in _FAMILIES:
37
+ if w.startswith(prefix):
38
+ return (ctx, out)
39
+ return None
40
+
41
+ def _provider_entry(model, provider: str='') -> tuple[str, dict]:
42
+ try:
43
+ import providers as _p
44
+ except Exception:
45
+ return (provider or '', {})
46
+ name = provider or _p.detect_provider(model)
47
+ return (name, _p.PROVIDERS.get(name, {}) or {})
48
+
49
+ def _int(value) -> int:
50
+ try:
51
+ return int(value or 0)
52
+ except (TypeError, ValueError):
53
+ return 0
54
+
55
+ def context_window(model, config: dict | None=None, provider: str='') -> int:
56
+ name, prov = _provider_entry(model, provider)
57
+ real = _int(prov.get('model_context_limits', {}).get(_bare(model)))
58
+ if not real:
59
+ real = _int(prov.get('context_limit'))
60
+ if not real:
61
+ fam = _family_limits(model)
62
+ real = fam[0] if fam else 0
63
+ if not real:
64
+ real = DEFAULT_CONTEXT
65
+ want = _int((config or {}).get('context_limit'))
66
+ return min(want, real) if want > 0 else real
67
+
68
+ def is_local(model, provider: str='') -> bool:
69
+ return _provider_entry(model, provider)[0] in LOCAL_PROVIDERS
70
+
71
+ def local_num_ctx(model, config: dict | None=None, provider: str='') -> int:
72
+ window = context_window(model, config, provider)
73
+ if _int((config or {}).get('context_limit')) > 0:
74
+ return window
75
+ return min(window, LOCAL_CONTEXT_AUTO)
76
+
77
+ def output_cap(model, config: dict | None=None, provider: str='', prompt_tokens: int=0) -> int:
78
+ name, prov = _provider_entry(model, provider)
79
+ fam = _family_limits(model)
80
+ limit = fam[1] if fam else 0
81
+ prov_cap = _int(prov.get('max_completion_tokens'))
82
+ if prov_cap:
83
+ limit = min(limit, prov_cap) if limit else prov_cap
84
+ if not limit:
85
+ limit = DEFAULT_OUTPUT
86
+ want = _int((config or {}).get('max_tokens'))
87
+ cap = min(want, limit) if want > 0 else limit
88
+ window = local_num_ctx(model, config, name) if name in LOCAL_PROVIDERS else context_window(model, config, name)
89
+ room = window - max(prompt_tokens, 0) - CONTEXT_SAFETY_MARGIN
90
+ cap = min(cap, max(room, MIN_COMPLETION))
91
+ floor = min(want, MIN_COMPLETION) if want > 0 else MIN_COMPLETION
92
+ return max(cap, floor)
93
+
94
+ def thinking_budget(model, config: dict | None=None, provider: str='', level: int | None=None, out_cap: int | None=None) -> int:
95
+ if level is None:
96
+ level = _thinking_level((config or {}).get('thinking', 0))
97
+ if level <= 0:
98
+ return 0
99
+ cap = out_cap if out_cap is not None else output_cap(model, config, provider)
100
+ if cap - MIN_ANSWER < MIN_THINKING:
101
+ return 0
102
+ want = _int((config or {}).get('thinking_budget'))
103
+ budget = want if want > 0 else _LEVEL_BUDGETS.get(level, _LEVEL_BUDGETS[3])
104
+ return max(min(budget, cap - MIN_ANSWER), MIN_THINKING)
105
+
106
+ def resolve(model, config: dict | None=None, provider: str='', prompt_tokens: int=0, level: int | None=None) -> Budget:
107
+ name, _prov = _provider_entry(model, provider)
108
+ ctx = context_window(model, config, name)
109
+ out = output_cap(model, config, name, prompt_tokens)
110
+ if level is None:
111
+ level = _thinking_level((config or {}).get('thinking', 0))
112
+ think = 0
113
+ if level > 0:
114
+ headroom = output_cap(model, {**(config or {}), 'max_tokens': 0}, name, prompt_tokens)
115
+ want = _int((config or {}).get('thinking_budget')) or _LEVEL_BUDGETS.get(level, _LEVEL_BUDGETS[3])
116
+ if _int((config or {}).get('max_tokens')) > 0:
117
+ out = min(max(out, MIN_THINKING + MIN_ANSWER), headroom)
118
+ else:
119
+ out = min(max(out, min(want + MIN_ANSWER, headroom)), headroom)
120
+ think = thinking_budget(model, config, name, level, out)
121
+ return Budget(context=ctx, output=out, thinking=think)
122
+
123
+ def describe(model, config: dict | None=None, provider: str='') -> str:
124
+ b = resolve(model, config, provider)
125
+ think = f'{b.thinking:,}' if b.thinking else 'off'
126
+ return f"context {b.context:,} · output {b.output:,} · thinking {think} (model: {_wire(model) or 'unknown'})"
127
+
128
+ def _bare(model) -> str:
129
+ m = str(model or '').strip()
130
+ return m.rsplit('/', 1)[-1] if '/' in m else m
131
+
132
+ def _thinking_level(value) -> int:
133
+ if isinstance(value, bool):
134
+ return 4 if value else 0
135
+ if isinstance(value, (int, float)):
136
+ return max(0, min(4, int(value)))
137
+ s = str(value or '').strip().lower()
138
+ if s in ('off', 'false', 'no', '0', ''):
139
+ return 0
140
+ if s in ('low', '1'):
141
+ return 1
142
+ if s in ('medium', 'med', '2'):
143
+ return 2
144
+ if s in ('high', '3'):
145
+ return 3
146
+ if s in ('on', 'true', 'yes', '4', 'max', 'ultra'):
147
+ return 4
148
+ return 0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes