dulus 3.13.4__tar.gz → 3.13.6__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 (387) hide show
  1. {dulus-3.13.4/dulus.egg-info → dulus-3.13.6}/PKG-INFO +1 -1
  2. {dulus-3.13.4 → dulus-3.13.6}/agent.py +13 -0
  3. {dulus-3.13.4 → dulus-3.13.6}/compaction.py +30 -0
  4. {dulus-3.13.4 → dulus-3.13.6}/context.py +22 -2
  5. {dulus-3.13.4 → dulus-3.13.6}/docs/CHANGELOG.md +21 -0
  6. {dulus-3.13.4 → dulus-3.13.6}/docs/news.md +4 -0
  7. {dulus-3.13.4 → dulus-3.13.6/dulus.egg-info}/PKG-INFO +1 -1
  8. {dulus-3.13.4 → dulus-3.13.6}/dulus.egg-info/SOURCES.txt +1 -0
  9. {dulus-3.13.4 → dulus-3.13.6}/dulus.py +29 -12
  10. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/background_tasks.py +17 -9
  11. {dulus-3.13.4 → dulus-3.13.6}/lookback.py +36 -3
  12. {dulus-3.13.4 → dulus-3.13.6}/memory/__init__.py +12 -1
  13. {dulus-3.13.4 → dulus-3.13.6}/memory/palace.py +153 -0
  14. {dulus-3.13.4 → dulus-3.13.6}/pyproject.toml +1 -1
  15. {dulus-3.13.4 → dulus-3.13.6}/tests/test_background_tasks.py +30 -13
  16. dulus-3.13.6/tests/test_gold_memory_preload.py +151 -0
  17. {dulus-3.13.4 → dulus-3.13.6}/tests/test_lookback_compact.py +53 -0
  18. {dulus-3.13.4 → dulus-3.13.6}/webchat_server.py +55 -0
  19. {dulus-3.13.4 → dulus-3.13.6}/LICENSE +0 -0
  20. {dulus-3.13.4 → dulus-3.13.6}/MANIFEST.in +0 -0
  21. {dulus-3.13.4 → dulus-3.13.6}/README.md +0 -0
  22. {dulus-3.13.4 → dulus-3.13.6}/algolia_search.py +0 -0
  23. {dulus-3.13.4 → dulus-3.13.6}/analytics.py +0 -0
  24. {dulus-3.13.4 → dulus-3.13.6}/backend/__init__.py +0 -0
  25. {dulus-3.13.4 → dulus-3.13.6}/backend/agents_bridge.py +0 -0
  26. {dulus-3.13.4 → dulus-3.13.6}/backend/compressor.py +0 -0
  27. {dulus-3.13.4 → dulus-3.13.6}/backend/context.py +0 -0
  28. {dulus-3.13.4 → dulus-3.13.6}/backend/githook.py +0 -0
  29. {dulus-3.13.4 → dulus-3.13.6}/backend/marketplace.py +0 -0
  30. {dulus-3.13.4 → dulus-3.13.6}/backend/mempalace_bridge.py +0 -0
  31. {dulus-3.13.4 → dulus-3.13.6}/backend/paths.py +0 -0
  32. {dulus-3.13.4 → dulus-3.13.6}/backend/personas.py +0 -0
  33. {dulus-3.13.4 → dulus-3.13.6}/backend/plugins.py +0 -0
  34. {dulus-3.13.4 → dulus-3.13.6}/backend/server.py +0 -0
  35. {dulus-3.13.4 → dulus-3.13.6}/backend/tasks.py +0 -0
  36. {dulus-3.13.4 → dulus-3.13.6}/banners/__init__.py +0 -0
  37. {dulus-3.13.4 → dulus-3.13.6}/banners/banner_rotator.py +0 -0
  38. {dulus-3.13.4 → dulus-3.13.6}/banners/banners_dulus_ai.txt +0 -0
  39. {dulus-3.13.4 → dulus-3.13.6}/batch_api.py +0 -0
  40. {dulus-3.13.4 → dulus-3.13.6}/checkpoint/__init__.py +0 -0
  41. {dulus-3.13.4 → dulus-3.13.6}/checkpoint/hooks.py +0 -0
  42. {dulus-3.13.4 → dulus-3.13.6}/checkpoint/store.py +0 -0
  43. {dulus-3.13.4 → dulus-3.13.6}/checkpoint/types.py +0 -0
  44. {dulus-3.13.4 → dulus-3.13.6}/cli_animations/__init__.py +0 -0
  45. {dulus-3.13.4 → dulus-3.13.6}/cli_animations/__main__.py +0 -0
  46. {dulus-3.13.4 → dulus-3.13.6}/cli_animations/ansi.py +0 -0
  47. {dulus-3.13.4 → dulus-3.13.6}/cli_animations/assets/ascii/dulus_logo.txt +0 -0
  48. {dulus-3.13.4 → dulus-3.13.6}/cli_animations/assets/ascii/eagle.txt +0 -0
  49. {dulus-3.13.4 → dulus-3.13.6}/cli_animations/assets/ascii/terminal_art.txt +0 -0
  50. {dulus-3.13.4 → dulus-3.13.6}/cli_animations/banners.py +0 -0
  51. {dulus-3.13.4 → dulus-3.13.6}/cli_animations/demo.py +0 -0
  52. {dulus-3.13.4 → dulus-3.13.6}/cli_animations/effects.py +0 -0
  53. {dulus-3.13.4 → dulus-3.13.6}/cli_animations/effort_slider.py +0 -0
  54. {dulus-3.13.4 → dulus-3.13.6}/cli_animations/progress.py +0 -0
  55. {dulus-3.13.4 → dulus-3.13.6}/cli_animations/py.typed +0 -0
  56. {dulus-3.13.4 → dulus-3.13.6}/cli_animations/spinners.py +0 -0
  57. {dulus-3.13.4 → dulus-3.13.6}/cli_animations/status.py +0 -0
  58. {dulus-3.13.4 → dulus-3.13.6}/clipboard_utils.py +0 -0
  59. {dulus-3.13.4 → dulus-3.13.6}/cloudsave.py +0 -0
  60. {dulus-3.13.4 → dulus-3.13.6}/common.py +0 -0
  61. {dulus-3.13.4 → dulus-3.13.6}/config.py +0 -0
  62. {dulus-3.13.4 → dulus-3.13.6}/data/__init__.py +0 -0
  63. {dulus-3.13.4 → dulus-3.13.6}/data/active_persona.json +0 -0
  64. {dulus-3.13.4 → dulus-3.13.6}/data/marketplace.json +0 -0
  65. {dulus-3.13.4 → dulus-3.13.6}/data/personas.json +0 -0
  66. {dulus-3.13.4 → dulus-3.13.6}/data/plugins/__init__.py +0 -0
  67. {dulus-3.13.4 → dulus-3.13.6}/data/plugins/composio/__init__.py +0 -0
  68. {dulus-3.13.4 → dulus-3.13.6}/data/plugins/composio/composio_plugin/__init__.py +0 -0
  69. {dulus-3.13.4 → dulus-3.13.6}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
  70. {dulus-3.13.4 → dulus-3.13.6}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
  71. {dulus-3.13.4 → dulus-3.13.6}/data/plugins/composio/plugin.json +0 -0
  72. {dulus-3.13.4 → dulus-3.13.6}/data/plugins/composio/plugin_tool.py +0 -0
  73. {dulus-3.13.4 → dulus-3.13.6}/data/tasks.json +0 -0
  74. {dulus-3.13.4 → dulus-3.13.6}/display_blocks.py +0 -0
  75. {dulus-3.13.4 → dulus-3.13.6}/docs/API.md +0 -0
  76. {dulus-3.13.4 → dulus-3.13.6}/docs/BRAND_GUIDE.md +0 -0
  77. {dulus-3.13.4 → dulus-3.13.6}/docs/CONTRIBUTING.md +0 -0
  78. {dulus-3.13.4 → dulus-3.13.6}/docs/DEPLOYMENT.md +0 -0
  79. {dulus-3.13.4 → dulus-3.13.6}/docs/FAQ.md +0 -0
  80. {dulus-3.13.4 → dulus-3.13.6}/docs/GETTING_STARTED.md +0 -0
  81. {dulus-3.13.4 → dulus-3.13.6}/docs/README.md +0 -0
  82. {dulus-3.13.4 → dulus-3.13.6}/docs/README_AR.md +0 -0
  83. {dulus-3.13.4 → dulus-3.13.6}/docs/README_EN.md +0 -0
  84. {dulus-3.13.4 → dulus-3.13.6}/docs/README_ES.md +0 -0
  85. {dulus-3.13.4 → dulus-3.13.6}/docs/README_FR.md +0 -0
  86. {dulus-3.13.4 → dulus-3.13.6}/docs/README_JA.md +0 -0
  87. {dulus-3.13.4 → dulus-3.13.6}/docs/README_KO.md +0 -0
  88. {dulus-3.13.4 → dulus-3.13.6}/docs/README_PT.md +0 -0
  89. {dulus-3.13.4 → dulus-3.13.6}/docs/README_RU.md +0 -0
  90. {dulus-3.13.4 → dulus-3.13.6}/docs/README_ZH.md +0 -0
  91. {dulus-3.13.4 → dulus-3.13.6}/docs/SECURITY.md +0 -0
  92. {dulus-3.13.4 → dulus-3.13.6}/docs/__init__.py +0 -0
  93. {dulus-3.13.4 → dulus-3.13.6}/docs/api.html +0 -0
  94. {dulus-3.13.4 → dulus-3.13.6}/docs/architecture.md +0 -0
  95. {dulus-3.13.4 → dulus-3.13.6}/docs/azure-speech-template.json +0 -0
  96. {dulus-3.13.4 → dulus-3.13.6}/docs/dashboard/index.html +0 -0
  97. {dulus-3.13.4 → dulus-3.13.6}/docs/divider.svg +0 -0
  98. {dulus-3.13.4 → dulus-3.13.6}/docs/dulus-bird.png +0 -0
  99. {dulus-3.13.4 → dulus-3.13.6}/docs/generate.py +0 -0
  100. {dulus-3.13.4 → dulus-3.13.6}/docs/index.html +0 -0
  101. {dulus-3.13.4 → dulus-3.13.6}/docs/nvidia-models.svg +0 -0
  102. {dulus-3.13.4 → dulus-3.13.6}/docs/particle-playground.html +0 -0
  103. {dulus-3.13.4 → dulus-3.13.6}/docs/personas/index.html +0 -0
  104. {dulus-3.13.4 → dulus-3.13.6}/docs/poetry-banner.png +0 -0
  105. {dulus-3.13.4 → dulus-3.13.6}/docs/preview.html +0 -0
  106. {dulus-3.13.4 → dulus-3.13.6}/docs/python-console.md +0 -0
  107. {dulus-3.13.4 → dulus-3.13.6}/docs/readme/auto-adapter.png +0 -0
  108. {dulus-3.13.4 → dulus-3.13.6}/docs/readme/dulus-os.png +0 -0
  109. {dulus-3.13.4 → dulus-3.13.6}/docs/readme/hero.png +0 -0
  110. {dulus-3.13.4 → dulus-3.13.6}/docs/readme/lookback_two_windows.png +0 -0
  111. {dulus-3.13.4 → dulus-3.13.6}/docs/readme/pyc_heap.svg +0 -0
  112. {dulus-3.13.4 → dulus-3.13.6}/docs/readme/pyc_receipts.svg +0 -0
  113. {dulus-3.13.4 → dulus-3.13.6}/docs/readme/pyc_trap.svg +0 -0
  114. {dulus-3.13.4 → dulus-3.13.6}/docs/readme/roundtable.png +0 -0
  115. {dulus-3.13.4 → dulus-3.13.6}/docs/readme/task-board.png +0 -0
  116. {dulus-3.13.4 → dulus-3.13.6}/docs/readme/webchat.png +0 -0
  117. {dulus-3.13.4 → dulus-3.13.6}/docs/sec-agents.svg +0 -0
  118. {dulus-3.13.4 → dulus-3.13.6}/docs/sec-brainstorm.svg +0 -0
  119. {dulus-3.13.4 → dulus-3.13.6}/docs/sec-bridges.svg +0 -0
  120. {dulus-3.13.4 → dulus-3.13.6}/docs/sec-features.svg +0 -0
  121. {dulus-3.13.4 → dulus-3.13.6}/docs/sec-freetier.svg +0 -0
  122. {dulus-3.13.4 → dulus-3.13.6}/docs/sec-memory.svg +0 -0
  123. {dulus-3.13.4 → dulus-3.13.6}/docs/sec-models.svg +0 -0
  124. {dulus-3.13.4 → dulus-3.13.6}/docs/sec-perms.svg +0 -0
  125. {dulus-3.13.4 → dulus-3.13.6}/docs/sec-plugins.svg +0 -0
  126. {dulus-3.13.4 → dulus-3.13.6}/docs/sec-quickstart.svg +0 -0
  127. {dulus-3.13.4 → dulus-3.13.6}/docs/sec-ssj.svg +0 -0
  128. {dulus-3.13.4 → dulus-3.13.6}/docs/spinners.svg +0 -0
  129. {dulus-3.13.4 → dulus-3.13.6}/docs/split-pane.svg +0 -0
  130. {dulus-3.13.4 → dulus-3.13.6}/docs/terminal-boot.svg +0 -0
  131. {dulus-3.13.4 → dulus-3.13.6}/docs/termux-edge.md +0 -0
  132. {dulus-3.13.4 → dulus-3.13.6}/dulus.egg-info/dependency_links.txt +0 -0
  133. {dulus-3.13.4 → dulus-3.13.6}/dulus.egg-info/entry_points.txt +0 -0
  134. {dulus-3.13.4 → dulus-3.13.6}/dulus.egg-info/requires.txt +0 -0
  135. {dulus-3.13.4 → dulus-3.13.6}/dulus.egg-info/top_level.txt +0 -0
  136. {dulus-3.13.4 → dulus-3.13.6}/dulus_account.py +0 -0
  137. {dulus-3.13.4 → dulus-3.13.6}/dulus_bar_client.py +0 -0
  138. {dulus-3.13.4 → dulus-3.13.6}/dulus_gui.py +0 -0
  139. {dulus-3.13.4 → dulus-3.13.6}/dulus_mcp/__init__.py +0 -0
  140. {dulus-3.13.4 → dulus-3.13.6}/dulus_mcp/client.py +0 -0
  141. {dulus-3.13.4 → dulus-3.13.6}/dulus_mcp/commands.py +0 -0
  142. {dulus-3.13.4 → dulus-3.13.6}/dulus_mcp/config.py +0 -0
  143. {dulus-3.13.4 → dulus-3.13.6}/dulus_mcp/hub.py +0 -0
  144. {dulus-3.13.4 → dulus-3.13.6}/dulus_mcp/tools.py +0 -0
  145. {dulus-3.13.4 → dulus-3.13.6}/dulus_mcp/types.py +0 -0
  146. {dulus-3.13.4 → dulus-3.13.6}/dulus_quick_menu.py +0 -0
  147. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/__init__.py +0 -0
  148. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/add_dir_manager.py +0 -0
  149. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/afk_mode.py +0 -0
  150. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/approval_runtime.py +0 -0
  151. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/clipboard_utils.py +0 -0
  152. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/diff_visualizer.py +0 -0
  153. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/export_import.py +0 -0
  154. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/hook_engine.py +0 -0
  155. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/notification_manager.py +0 -0
  156. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/session_fork.py +0 -0
  157. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/shell_mode.py +0 -0
  158. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/todo_visualizer.py +0 -0
  159. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/tree_ls.py +0 -0
  160. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/wire_events.py +0 -0
  161. {dulus-3.13.4 → dulus-3.13.6}/dulus_tools/yolo_mode.py +0 -0
  162. {dulus-3.13.4 → dulus-3.13.6}/file_filter.py +0 -0
  163. {dulus-3.13.4 → dulus-3.13.6}/fs_compass.py +0 -0
  164. {dulus-3.13.4 → dulus-3.13.6}/governance.py +0 -0
  165. {dulus-3.13.4 → dulus-3.13.6}/gui/__init__.py +0 -0
  166. {dulus-3.13.4 → dulus-3.13.6}/gui/agent_bridge.py +0 -0
  167. {dulus-3.13.4 → dulus-3.13.6}/gui/chat_widget.py +0 -0
  168. {dulus-3.13.4 → dulus-3.13.6}/gui/main_window.py +0 -0
  169. {dulus-3.13.4 → dulus-3.13.6}/gui/personas.py +0 -0
  170. {dulus-3.13.4 → dulus-3.13.6}/gui/sandbox_server.py +0 -0
  171. {dulus-3.13.4 → dulus-3.13.6}/gui/session_utils.py +0 -0
  172. {dulus-3.13.4 → dulus-3.13.6}/gui/settings_dialog.py +0 -0
  173. {dulus-3.13.4 → dulus-3.13.6}/gui/sidebar.py +0 -0
  174. {dulus-3.13.4 → dulus-3.13.6}/gui/tasks_view.py +0 -0
  175. {dulus-3.13.4 → dulus-3.13.6}/gui/themes.py +0 -0
  176. {dulus-3.13.4 → dulus-3.13.6}/gui/tool_panel.py +0 -0
  177. {dulus-3.13.4 → dulus-3.13.6}/input.py +0 -0
  178. {dulus-3.13.4 → dulus-3.13.6}/license_manager.py +0 -0
  179. {dulus-3.13.4 → dulus-3.13.6}/memory/audit.py +0 -0
  180. {dulus-3.13.4 → dulus-3.13.6}/memory/consolidator.py +0 -0
  181. {dulus-3.13.4 → dulus-3.13.6}/memory/context.py +0 -0
  182. {dulus-3.13.4 → dulus-3.13.6}/memory/mempalace_bridge.py +0 -0
  183. {dulus-3.13.4 → dulus-3.13.6}/memory/offload.py +0 -0
  184. {dulus-3.13.4 → dulus-3.13.6}/memory/scan.py +0 -0
  185. {dulus-3.13.4 → dulus-3.13.6}/memory/seeds/insights/adaptation_guides.md +0 -0
  186. {dulus-3.13.4 → dulus-3.13.6}/memory/seeds/insights/askuserquestion_after_numbered_list.md +0 -0
  187. {dulus-3.13.4 → dulus-3.13.6}/memory/seeds/insights/background_jobs_no_extra_timers.md +0 -0
  188. {dulus-3.13.4 → dulus-3.13.6}/memory/seeds/insights/cleverness_over_resources.md +0 -0
  189. {dulus-3.13.4 → dulus-3.13.6}/memory/seeds/insights/corporate_tls_breaks_curl_cffi.md +0 -0
  190. {dulus-3.13.4 → dulus-3.13.6}/memory/seeds/insights/hardened_rules.md +0 -0
  191. {dulus-3.13.4 → dulus-3.13.6}/memory/seeds/insights/investigator_workflow_heuristic.md +0 -0
  192. {dulus-3.13.4 → dulus-3.13.6}/memory/seeds/insights/native_code_over_sketchy_plugins.md +0 -0
  193. {dulus-3.13.4 → dulus-3.13.6}/memory/seeds/insights/no_personal_info_in_code.md +0 -0
  194. {dulus-3.13.4 → dulus-3.13.6}/memory/seeds/insights/searches_include_modified_date.md +0 -0
  195. {dulus-3.13.4 → dulus-3.13.6}/memory/seeds/insights/tmux_send_keys_power_patterns.md +0 -0
  196. {dulus-3.13.4 → dulus-3.13.6}/memory/seeds/insights/token_optimization_technique.md +0 -0
  197. {dulus-3.13.4 → dulus-3.13.6}/memory/seeds/insights/tool_chaining_capability.md +0 -0
  198. {dulus-3.13.4 → dulus-3.13.6}/memory/seeds/insights/webbridge_evaluate_first_pattern.md +0 -0
  199. {dulus-3.13.4 → dulus-3.13.6}/memory/seeds/short_memory.md +0 -0
  200. {dulus-3.13.4 → dulus-3.13.6}/memory/sessions.py +0 -0
  201. {dulus-3.13.4 → dulus-3.13.6}/memory/store.py +0 -0
  202. {dulus-3.13.4 → dulus-3.13.6}/memory/tools.py +0 -0
  203. {dulus-3.13.4 → dulus-3.13.6}/memory/types.py +0 -0
  204. {dulus-3.13.4 → dulus-3.13.6}/memory/vector_search.py +0 -0
  205. {dulus-3.13.4 → dulus-3.13.6}/model_params.py +0 -0
  206. {dulus-3.13.4 → dulus-3.13.6}/multi_agent/__init__.py +0 -0
  207. {dulus-3.13.4 → dulus-3.13.6}/multi_agent/subagent.py +0 -0
  208. {dulus-3.13.4 → dulus-3.13.6}/multi_agent/tools.py +0 -0
  209. {dulus-3.13.4 → dulus-3.13.6}/paste_placeholders.py +0 -0
  210. {dulus-3.13.4 → dulus-3.13.6}/personality_config.py +0 -0
  211. {dulus-3.13.4 → dulus-3.13.6}/plugin/__init__.py +0 -0
  212. {dulus-3.13.4 → dulus-3.13.6}/plugin/autoadapter.py +0 -0
  213. {dulus-3.13.4 → dulus-3.13.6}/plugin/loader.py +0 -0
  214. {dulus-3.13.4 → dulus-3.13.6}/plugin/recommend.py +0 -0
  215. {dulus-3.13.4 → dulus-3.13.6}/plugin/store.py +0 -0
  216. {dulus-3.13.4 → dulus-3.13.6}/plugin/types.py +0 -0
  217. {dulus-3.13.4 → dulus-3.13.6}/profiles.py +0 -0
  218. {dulus-3.13.4 → dulus-3.13.6}/providers.py +0 -0
  219. {dulus-3.13.4 → dulus-3.13.6}/pyconsole.py +0 -0
  220. {dulus-3.13.4 → dulus-3.13.6}/sandbox/README.md +0 -0
  221. {dulus-3.13.4 → dulus-3.13.6}/sandbox/__init__.py +0 -0
  222. {dulus-3.13.4 → dulus-3.13.6}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
  223. {dulus-3.13.4 → dulus-3.13.6}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
  224. {dulus-3.13.4 → dulus-3.13.6}/sandbox/dist/index.html +0 -0
  225. {dulus-3.13.4 → dulus-3.13.6}/sandbox/dist/wallpaper-default.jpg +0 -0
  226. {dulus-3.13.4 → dulus-3.13.6}/sandbox/dist/wallpapers/default.jpeg +0 -0
  227. {dulus-3.13.4 → dulus-3.13.6}/sandbox/dist/wallpapers/light.jpeg +0 -0
  228. {dulus-3.13.4 → dulus-3.13.6}/sandbox/dist/wallpapers/nature.jpeg +0 -0
  229. {dulus-3.13.4 → dulus-3.13.6}/sandbox/dist/wallpapers/tech.jpeg +0 -0
  230. {dulus-3.13.4 → dulus-3.13.6}/sandbox/info.md +0 -0
  231. {dulus-3.13.4 → dulus-3.13.6}/sandbox_bootstrap.py +0 -0
  232. {dulus-3.13.4 → dulus-3.13.6}/setup.cfg +0 -0
  233. {dulus-3.13.4 → dulus-3.13.6}/skill/__init__.py +0 -0
  234. {dulus-3.13.4 → dulus-3.13.6}/skill/builtin.py +0 -0
  235. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/LICENSE-obsidian-skills +0 -0
  236. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/README.md +0 -0
  237. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/defuddle/SKILL.md +0 -0
  238. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/json-canvas/SKILL.md +0 -0
  239. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/json-canvas/references/EXAMPLES.md +0 -0
  240. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/obsidian-bases/SKILL.md +0 -0
  241. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/obsidian-bases/references/FUNCTIONS_REFERENCE.md +0 -0
  242. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/obsidian-cli/SKILL.md +0 -0
  243. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/obsidian-markdown/SKILL.md +0 -0
  244. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/obsidian-markdown/references/CALLOUTS.md +0 -0
  245. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/obsidian-markdown/references/EMBEDS.md +0 -0
  246. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/obsidian-markdown/references/PROPERTIES.md +0 -0
  247. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/README.md +0 -0
  248. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/SKILL.md +0 -0
  249. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/addresses.json +0 -0
  250. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/advancedbackgroundchecks.json +0 -0
  251. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/beenverified.json +0 -0
  252. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/clustal.json +0 -0
  253. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/clustrmaps.json +0 -0
  254. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/cyberbackgroundchecks.json +0 -0
  255. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/familytreenow.json +0 -0
  256. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/fastpeoplesearch.json +0 -0
  257. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/intelius.json +0 -0
  258. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/mylife.json +0 -0
  259. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/nuwber.json +0 -0
  260. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/peekyou.json +0 -0
  261. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/peoplefinders.json +0 -0
  262. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/radaris.json +0 -0
  263. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/rehold.json +0 -0
  264. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/searchpeoplefree.json +0 -0
  265. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/socialcatfish.json +0 -0
  266. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/spokeo.json +0 -0
  267. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/thatsthem.json +0 -0
  268. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/truepeoplesearch.json +0 -0
  269. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/usphonebook.json +0 -0
  270. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/brokers/whitepages.json +0 -0
  271. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/legal/ccpa.md +0 -0
  272. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/legal/drop.md +0 -0
  273. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/legal/gdpr.md +0 -0
  274. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/methods.md +0 -0
  275. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/site-playbooks.md +0 -0
  276. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/references/state-machine.md +0 -0
  277. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/autopilot.py +0 -0
  278. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/badbool.py +0 -0
  279. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/brokers.py +0 -0
  280. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/cdp.py +0 -0
  281. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/config.py +0 -0
  282. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/crypto.py +0 -0
  283. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/dossier.py +0 -0
  284. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/email_modes.py +0 -0
  285. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/emailer.py +0 -0
  286. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/ledger.py +0 -0
  287. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/legal.py +0 -0
  288. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/paths.py +0 -0
  289. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/pdd.py +0 -0
  290. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/registry.py +0 -0
  291. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/report.py +0 -0
  292. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/scan.py +0 -0
  293. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/storage.py +0 -0
  294. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/tiers.py +0 -0
  295. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/scripts/vectors.py +0 -0
  296. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/templates/consent/authorization.md +0 -0
  297. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/templates/emails/ccpa-authorized-agent.txt +0 -0
  298. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/templates/emails/ccpa-deletion.txt +0 -0
  299. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/templates/emails/ccpa-indirect-deletion.txt +0 -0
  300. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/templates/emails/gdpr-erasure.txt +0 -0
  301. {dulus-3.13.4 → dulus-3.13.6}/skill/bundled/unbroker/templates/emails/generic-optout.txt +0 -0
  302. {dulus-3.13.4 → dulus-3.13.6}/skill/clawhub.py +0 -0
  303. {dulus-3.13.4 → dulus-3.13.6}/skill/executor.py +0 -0
  304. {dulus-3.13.4 → dulus-3.13.6}/skill/loader.py +0 -0
  305. {dulus-3.13.4 → dulus-3.13.6}/skill/tools.py +0 -0
  306. {dulus-3.13.4 → dulus-3.13.6}/skills.py +0 -0
  307. {dulus-3.13.4 → dulus-3.13.6}/soul.py +0 -0
  308. {dulus-3.13.4 → dulus-3.13.6}/spinner.py +0 -0
  309. {dulus-3.13.4 → dulus-3.13.6}/steer_input.py +0 -0
  310. {dulus-3.13.4 → dulus-3.13.6}/subagent.py +0 -0
  311. {dulus-3.13.4 → dulus-3.13.6}/task/__init__.py +0 -0
  312. {dulus-3.13.4 → dulus-3.13.6}/task/store.py +0 -0
  313. {dulus-3.13.4 → dulus-3.13.6}/task/tools.py +0 -0
  314. {dulus-3.13.4 → dulus-3.13.6}/task/types.py +0 -0
  315. {dulus-3.13.4 → dulus-3.13.6}/tests/test_afk_yolo.py +0 -0
  316. {dulus-3.13.4 → dulus-3.13.6}/tests/test_approval_runtime.py +0 -0
  317. {dulus-3.13.4 → dulus-3.13.6}/tests/test_background_task_tools.py +0 -0
  318. {dulus-3.13.4 → dulus-3.13.6}/tests/test_chatgpt_oauth.py +0 -0
  319. {dulus-3.13.4 → dulus-3.13.6}/tests/test_checkpoint.py +0 -0
  320. {dulus-3.13.4 → dulus-3.13.6}/tests/test_cli_animations_integration.py +0 -0
  321. {dulus-3.13.4 → dulus-3.13.6}/tests/test_clipboard_utils.py +0 -0
  322. {dulus-3.13.4 → dulus-3.13.6}/tests/test_compaction.py +0 -0
  323. {dulus-3.13.4 → dulus-3.13.6}/tests/test_composio_tool_generator.py +0 -0
  324. {dulus-3.13.4 → dulus-3.13.6}/tests/test_diff_view.py +0 -0
  325. {dulus-3.13.4 → dulus-3.13.6}/tests/test_diff_visualization.py +0 -0
  326. {dulus-3.13.4 → dulus-3.13.6}/tests/test_display_blocks.py +0 -0
  327. {dulus-3.13.4 → dulus-3.13.6}/tests/test_dulus_account.py +0 -0
  328. {dulus-3.13.4 → dulus-3.13.6}/tests/test_export_import.py +0 -0
  329. {dulus-3.13.4 → dulus-3.13.6}/tests/test_grok_usage.py +0 -0
  330. {dulus-3.13.4 → dulus-3.13.6}/tests/test_hook_engine.py +0 -0
  331. {dulus-3.13.4 → dulus-3.13.6}/tests/test_injection_fix.py +0 -0
  332. {dulus-3.13.4 → dulus-3.13.6}/tests/test_json_protocol.py +0 -0
  333. {dulus-3.13.4 → dulus-3.13.6}/tests/test_license.py +0 -0
  334. {dulus-3.13.4 → dulus-3.13.6}/tests/test_lookback_cache.py +0 -0
  335. {dulus-3.13.4 → dulus-3.13.6}/tests/test_mcp.py +0 -0
  336. {dulus-3.13.4 → dulus-3.13.6}/tests/test_mcp_algolia.py +0 -0
  337. {dulus-3.13.4 → dulus-3.13.6}/tests/test_memory.py +0 -0
  338. {dulus-3.13.4 → dulus-3.13.6}/tests/test_memory_home_resolution.py +0 -0
  339. {dulus-3.13.4 → dulus-3.13.6}/tests/test_notification_manager.py +0 -0
  340. {dulus-3.13.4 → dulus-3.13.6}/tests/test_plugin.py +0 -0
  341. {dulus-3.13.4 → dulus-3.13.6}/tests/test_profile_isolation.py +0 -0
  342. {dulus-3.13.4 → dulus-3.13.6}/tests/test_profile_plugin_scope.py +0 -0
  343. {dulus-3.13.4 → dulus-3.13.6}/tests/test_provider_errors.py +0 -0
  344. {dulus-3.13.4 → dulus-3.13.6}/tests/test_session_fork.py +0 -0
  345. {dulus-3.13.4 → dulus-3.13.6}/tests/test_shell_detection.py +0 -0
  346. {dulus-3.13.4 → dulus-3.13.6}/tests/test_shell_mode.py +0 -0
  347. {dulus-3.13.4 → dulus-3.13.6}/tests/test_skills.py +0 -0
  348. {dulus-3.13.4 → dulus-3.13.6}/tests/test_skillssh_algolia.py +0 -0
  349. {dulus-3.13.4 → dulus-3.13.6}/tests/test_steer_input.py +0 -0
  350. {dulus-3.13.4 → dulus-3.13.6}/tests/test_subagent.py +0 -0
  351. {dulus-3.13.4 → dulus-3.13.6}/tests/test_task.py +0 -0
  352. {dulus-3.13.4 → dulus-3.13.6}/tests/test_telegram_buffer.py +0 -0
  353. {dulus-3.13.4 → dulus-3.13.6}/tests/test_think_tool.py +0 -0
  354. {dulus-3.13.4 → dulus-3.13.6}/tests/test_todo_tool.py +0 -0
  355. {dulus-3.13.4 → dulus-3.13.6}/tests/test_todo_visualization.py +0 -0
  356. {dulus-3.13.4 → dulus-3.13.6}/tests/test_tool_registry.py +0 -0
  357. {dulus-3.13.4 → dulus-3.13.6}/tests/test_tree_ls.py +0 -0
  358. {dulus-3.13.4 → dulus-3.13.6}/tests/test_voice.py +0 -0
  359. {dulus-3.13.4 → dulus-3.13.6}/tests/test_webbridge_tabs.py +0 -0
  360. {dulus-3.13.4 → dulus-3.13.6}/tests/test_wire_events.py +0 -0
  361. {dulus-3.13.4 → dulus-3.13.6}/tmux_tools.py +0 -0
  362. {dulus-3.13.4 → dulus-3.13.6}/token_budget.py +0 -0
  363. {dulus-3.13.4 → dulus-3.13.6}/tool_registry.py +0 -0
  364. {dulus-3.13.4 → dulus-3.13.6}/tools.py +0 -0
  365. {dulus-3.13.4 → dulus-3.13.6}/tools_background.py +0 -0
  366. {dulus-3.13.4 → dulus-3.13.6}/tools_think.py +0 -0
  367. {dulus-3.13.4 → dulus-3.13.6}/tools_todo.py +0 -0
  368. {dulus-3.13.4 → dulus-3.13.6}/ui/__init__.py +0 -0
  369. {dulus-3.13.4 → dulus-3.13.6}/ui/input.py +0 -0
  370. {dulus-3.13.4 → dulus-3.13.6}/ui/render.py +0 -0
  371. {dulus-3.13.4 → dulus-3.13.6}/updater.py +0 -0
  372. {dulus-3.13.4 → dulus-3.13.6}/voice/__init__.py +0 -0
  373. {dulus-3.13.4 → dulus-3.13.6}/voice/audio_utils.py +0 -0
  374. {dulus-3.13.4 → dulus-3.13.6}/voice/keyterms.py +0 -0
  375. {dulus-3.13.4 → dulus-3.13.6}/voice/recorder.py +0 -0
  376. {dulus-3.13.4 → dulus-3.13.6}/voice/stt.py +0 -0
  377. {dulus-3.13.4 → dulus-3.13.6}/voice/tts.py +0 -0
  378. {dulus-3.13.4 → dulus-3.13.6}/voice/wake_word.py +0 -0
  379. {dulus-3.13.4 → dulus-3.13.6}/webbridge/__init__.py +0 -0
  380. {dulus-3.13.4 → dulus-3.13.6}/webbridge/core.py +0 -0
  381. {dulus-3.13.4 → dulus-3.13.6}/webbridge/tools.py +0 -0
  382. {dulus-3.13.4 → dulus-3.13.6}/webchat.py +0 -0
  383. {dulus-3.13.4 → dulus-3.13.6}/webchat_ui/__init__.py +0 -0
  384. {dulus-3.13.4 → dulus-3.13.6}/webchat_ui/app.js +0 -0
  385. {dulus-3.13.4 → dulus-3.13.6}/webchat_ui/index.html +0 -0
  386. {dulus-3.13.4 → dulus-3.13.6}/webchat_ui/style.css +0 -0
  387. {dulus-3.13.4 → dulus-3.13.6}/welcome.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: dulus
3
- Version: 3.13.4
3
+ Version: 3.13.6
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>
@@ -206,6 +206,19 @@ def run(
206
206
  except Exception:
207
207
  _api_source, _lb_meta = state.messages, {"enabled": False, "truncated": False}
208
208
 
209
+ # Strip GUI/REPL baseline display blobs (soul/gold/welcome). The model
210
+ # already has them via system prompt; leaving them as role:assistant
211
+ # made web-history consolidate drop everything before the last
212
+ # assistant (= the gold blob itself) on first turn.
213
+ try:
214
+ from memory import is_baseline_display_message
215
+ _api_source = [
216
+ m for m in _api_source
217
+ if not is_baseline_display_message(m)
218
+ ]
219
+ except Exception:
220
+ pass
221
+
209
222
  # Sanitize message contents before sending to API (surrogate safety)
210
223
  _safe_messages = []
211
224
  for m in _api_source:
@@ -678,6 +678,13 @@ def save_loopback_archive(messages: list, state=None, config: dict | None = None
678
678
  Writes ~/.dulus/loopback_archives/archive_<session>.json and binds
679
679
  config["_loopback_archive_path"] + config["_loopback_archive"] (in-memory
680
680
  copy) for the Loopback tool. Returns the path or None on failure.
681
+
682
+ CRITICAL — second-/Nth-compact guard:
683
+ A later ``/compact`` runs against an *already slimmed* ``state.messages``.
684
+ Naively rewriting the archive would overwrite the original full history
685
+ with the post-compact crumbs. We NEVER shrink a durable archive: if an
686
+ in-memory or on-disk copy is longer than ``messages``, keep the longer one
687
+ and only refresh the binding.
681
688
  """
682
689
  if not messages:
683
690
  return None
@@ -685,6 +692,29 @@ def save_loopback_archive(messages: list, state=None, config: dict | None = None
685
692
  LOOPBACK_ARCHIVE_DIR.mkdir(parents=True, exist_ok=True)
686
693
  sid = _session_key(state, config)
687
694
  path = LOOPBACK_ARCHIVE_DIR / f"archive_{sid}.json"
695
+
696
+ # Resolve the longest known full archive for this session.
697
+ prev: list = []
698
+ if isinstance(config, dict):
699
+ live = config.get("_loopback_archive")
700
+ if isinstance(live, list) and live:
701
+ prev = live
702
+ if not prev and path.exists():
703
+ try:
704
+ disk = json.loads(path.read_text(encoding="utf-8"))
705
+ disk_msgs = disk.get("messages") if isinstance(disk, dict) else None
706
+ if isinstance(disk_msgs, list) and disk_msgs:
707
+ prev = disk_msgs
708
+ except Exception:
709
+ prev = []
710
+
711
+ if prev and len(prev) > len(messages):
712
+ # Refuse to shrink. Keep the fuller archive as the durable source.
713
+ if isinstance(config, dict):
714
+ config["_loopback_archive"] = prev
715
+ config["_loopback_archive_path"] = str(path)
716
+ return path
717
+
688
718
  payload = {
689
719
  "session_id": sid,
690
720
  "timestamp": time.strftime("%Y%m%d_%H%M%S"),
@@ -442,12 +442,28 @@ def _resolve_reply_language(config: dict | None) -> str:
442
442
  return raw
443
443
 
444
444
 
445
+
446
+ def _append_baseline_fragments(prompt: str) -> str:
447
+ """Attach soul + gold system fragments (always-on baseline)."""
448
+ try:
449
+ from memory import soul_system_fragment, gold_system_fragment
450
+ _soul = soul_system_fragment()
451
+ if _soul:
452
+ prompt += "\n\n" + _soul
453
+ _gold = gold_system_fragment()
454
+ if _gold:
455
+ prompt += "\n\n" + _gold
456
+ except Exception:
457
+ pass
458
+ return prompt
459
+
460
+
445
461
  def build_system_prompt(config: dict | None = None) -> str:
446
462
  import platform
447
463
  model_lower = (config.get("model", "") if config else "").lower()
448
464
  is_deepseek_r1 = "deepseek-r1" in model_lower or "deepseek-reasoner" in model_lower
449
465
  if is_deepseek_r1 and config and config.get("deep_override", False):
450
- return _build_ollama_system_prompt(config)
466
+ return _append_baseline_fragments(_build_ollama_system_prompt(config))
451
467
 
452
468
  auto_show = "ON" if (not config or config.get("auto_show", True)) else "OFF"
453
469
  lite = bool(config and config.get("lite_mode"))
@@ -470,8 +486,12 @@ def build_system_prompt(config: dict | None = None) -> str:
470
486
  dulus_md="" if lite else get_dulus_md(),
471
487
  )
472
488
 
489
+ # Soul + Gold baseline ALWAYS (even in lite) — model source of truth.
490
+ # Display copies in chat are GUI-only and get stripped before the API call.
491
+ prompt = _append_baseline_fragments(prompt)
492
+
473
493
  if lite:
474
- # Bail early — minimal prompt only.
494
+ # Bail early — minimal prompt only (baseline already attached).
475
495
  return prompt
476
496
 
477
497
  try:
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.13.6] - 2026-08-31
11
+
12
+ ### Fixed
13
+ - **WebChat CONFIG None guard**: `CONFIG` is checked before `.pop(...)` so pyright/static quality stays clean when config is unset
14
+ - **Background tasks on Windows**: worker launch no longer goes through bash-quoted `python -c`; Windows-safe `_worker.py` path keeps win-3.13 CI stable
15
+
16
+ ### Tests
17
+ - Quality CI green on Ubuntu + Windows · Python 3.11 + 3.13 · Types and package green
18
+
19
+ ## [3.13.5] - 2026-08-31
20
+
21
+ ### Fixed
22
+ - **Gold / short_memory baseline**: curated gold memories (incl. `short_memory`) and soul now ride in `build_system_prompt` every turn — always-on, **not** gated by `/mem_palace`
23
+ - **First-turn amnesia**: agent + lookback strip GUI/REPL gold/soul/welcome *display* blobs before the provider call, so web-history consolidate no longer treats gold as “last assistant reply” and drops real context
24
+ - **MemPalace dual-path noise**: per-turn inject (CLI + WebChat) skips `short_memory` / soul — already in the system prompt
25
+ - **Second `/compact` archive wipe**: `save_loopback_archive` refuses to shrink; Loopback keeps the full pre-compact history across N compacts
26
+ - **WebChat gold preload**: session seeds gold *display* copies for the transcript; model source of truth is still the system prompt (public GUI stays as-is — this is server/engine, not an Interant GUI port)
27
+
28
+ ### Tests
29
+ - `test_gold_memory_preload` + `test_lookback_compact` (11 passed)
30
+
10
31
  ## [3.13.4] - 2026-08-31
11
32
 
12
33
  ### Fixed
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## 🔥🔥🔥 News (Pacific Time)
4
4
 
5
+ - August 31, 2026 (**v3.13.6**): **CI quality locked green.** WebChat guards `CONFIG` before pop (pyright clean) and background workers launch Windows-safe without bash-quoted `python -c` — win-3.13 flake gone. Hotfix patch on top of 3.13.5. `pip install -U dulus`. 🦅
6
+
7
+ - August 31, 2026 (**v3.13.5**): **🏆 short_memory stopped vanishing.** Gold memories + soul now live in the **system prompt** every turn (always-on — `/mem_palace off` no longer starves them). WebChat seeds gold for the transcript; the agent/lookback strip those display blobs before the provider call so first-turn consolidate can’t eat your context. Second `/compact` no longer wipes the Loopback archive. Engine + WebChat fix — not a desktop-GUI rewrite. `pip install -U dulus`. 🦅
8
+
5
9
  - 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
10
 
7
11
  - 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`. 🦅
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: dulus
3
- Version: 3.13.4
3
+ Version: 3.13.6
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>
@@ -332,6 +332,7 @@ tests/test_diff_visualization.py
332
332
  tests/test_display_blocks.py
333
333
  tests/test_dulus_account.py
334
334
  tests/test_export_import.py
335
+ tests/test_gold_memory_preload.py
335
336
  tests/test_grok_usage.py
336
337
  tests/test_hook_engine.py
337
338
  tests/test_injection_fix.py
@@ -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.4" # dev fallback — keep in sync with pyproject.toml
398
+ VERSION = "3.13.6" # 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
@@ -3570,6 +3570,8 @@ def cmd_mem_palace(args: str, _state, config) -> bool:
3570
3570
  config["mem_palace"] = not config.get("mem_palace", True)
3571
3571
  state_str = "ON" if config["mem_palace"] else "OFF"
3572
3572
  ok(f"MemPalace auto-injection: {state_str}")
3573
+ if not config["mem_palace"]:
3574
+ info("Gold memories (incl. short_memory) still load at session start.")
3573
3575
  save_config(config)
3574
3576
  return True
3575
3577
 
@@ -12227,18 +12229,21 @@ def repl(config: dict, initial_prompt: str | None = None):
12227
12229
  except Exception as e:
12228
12230
  startup_status_msgs.append(clr(f" ⚠ Schema inject skip: {e}", "yellow"))
12229
12231
 
12230
- # ── Gold Memories Auto-Load ───────────────────────────────────────────────
12231
- # Memories marked with `gold: true` (via /memory permanent) are injected
12232
- # at startup the same way as Soul.
12232
+ # ── Gold Memories Auto-Load (GUI/REPL display copies) ─────────────────────
12233
+ # Model source of truth is build_system_prompt → gold_system_fragment().
12234
+ # These assistant-role copies are for the transcript/GUI only and get
12235
+ # stripped before the provider call (agent.py + lookback). Never gated
12236
+ # by /mem_palace (that toggle is only for per-turn semantic search).
12233
12237
  try:
12234
- from memory import load_index
12235
- gold_entries = [e for e in load_index("all") if getattr(e, "gold", False)]
12236
- for e in gold_entries:
12237
- state.messages.append({
12238
- "role": "assistant",
12239
- "content": f"[Golden Memory Loaded: {e.name}]\n\n{e.content}",
12240
- })
12241
- startup_status_msgs.append(clr(f" 🏆 Gold memory loaded: {e.name}", "yellow", "bold"))
12238
+ from memory import gold_context_messages, gold_system_fragment
12239
+ _gold_in_system = bool(gold_system_fragment())
12240
+ for _msg in gold_context_messages():
12241
+ state.messages.append(_msg)
12242
+ _gold_name = _msg["content"].split("]", 1)[0].removeprefix("[Golden Memory Loaded: ")
12243
+ _where = "system+GUI" if _gold_in_system else "GUI only"
12244
+ startup_status_msgs.append(
12245
+ clr(f" 🏆 Gold memory loaded ({_where}): {_gold_name}", "yellow", "bold")
12246
+ )
12242
12247
  except Exception:
12243
12248
  pass
12244
12249
 
@@ -12735,6 +12740,18 @@ def repl(config: dict, initial_prompt: str | None = None):
12735
12740
  for _h in _raw_hits:
12736
12741
  _mp_log(f" hit: score={float(_h.get('keyword_score', 0.0)):.3f} {_h.get('name','?')}", "dim")
12737
12742
  _kept = [h for h in _raw_hits if float(h.get("keyword_score", 0.0)) >= _MIN_SCORE]
12743
+ # Skip short_memory/soul — already in system-prompt baseline.
12744
+ try:
12745
+ from memory import is_baseline_memory_name
12746
+ _kept = [
12747
+ h for h in _kept
12748
+ if not is_baseline_memory_name(h.get("name"))
12749
+ and not is_baseline_memory_name(
12750
+ (h.get("description") or "").split()[:1][0] if h.get("description") else None
12751
+ )
12752
+ ]
12753
+ except Exception:
12754
+ pass
12738
12755
  # ── Dedup: skip memories already injected earlier in this session.
12739
12756
  # Key by content hash (not name) because mempalace often returns
12740
12757
  # generic names like "palace" for every hit in a wing — name-based
@@ -513,13 +513,21 @@ class BackgroundTaskManager:
513
513
  env["DULUS_BG_TASK"] = "1"
514
514
  env["DULUS_BG_TIMEOUT"] = str(timeout_s)
515
515
 
516
- # Escape single quotes in the command to avoid breaking the wrapper
517
- safe_command = command.replace("'", "'\"'\"'")
518
- script = f"cd '{cwd}' && {safe_command}"
516
+ # Write a tiny runner script so cwd/command never pass through shell
517
+ # quoting (bash `'"'"'` tricks + Python -c single quotes break hard on
518
+ # Windows paths like C:\Users\... and on echo 'HELLO').
519
+ runner = task_dir / "_worker.py"
520
+ runner.write_text(
521
+ "import os, subprocess, sys\n"
522
+ f"os.chdir({cwd!r})\n"
523
+ f"raise SystemExit(subprocess.call({command!r}, shell=True))\n",
524
+ encoding="utf-8",
525
+ )
519
526
 
527
+ log_fh = open(task_dir / "output.log", "w", encoding="utf-8")
520
528
  kwargs: dict[str, Any] = {
521
529
  "stdin": subprocess.DEVNULL,
522
- "stdout": open(task_dir / "output.log", "w", encoding="utf-8"),
530
+ "stdout": log_fh,
523
531
  "stderr": subprocess.STDOUT,
524
532
  "cwd": cwd,
525
533
  "env": env,
@@ -527,13 +535,13 @@ class BackgroundTaskManager:
527
535
 
528
536
  if os.name != "nt":
529
537
  kwargs["start_new_session"] = True
538
+ else:
539
+ # Detach from the console so the parent can exit cleanly on CI.
540
+ create_new = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
541
+ kwargs["creationflags"] = create_new
530
542
 
531
543
  process = subprocess.Popen(
532
- [
533
- sys.executable,
534
- "-c",
535
- f"import subprocess, sys; sys.exit(subprocess.call('{script}', shell=True))",
536
- ],
544
+ [sys.executable, str(runner)],
537
545
  **kwargs,
538
546
  )
539
547
  return process.pid
@@ -155,6 +155,25 @@ def find_lookback_start(messages: list, turns: int) -> int:
155
155
  return start
156
156
 
157
157
 
158
+
159
+ def _strip_baseline_display(messages: list) -> list:
160
+ """Drop soul/gold/welcome GUI display blobs from an API window.
161
+
162
+ Those ride in the system prompt now (see memory.gold_system_fragment /
163
+ soul_system_fragment). Keeping them as role:assistant in the API payload
164
+ made web-history consolidate treat gold as "last assistant reply".
165
+ """
166
+ if not messages:
167
+ return messages
168
+ try:
169
+ from memory import is_baseline_display_message
170
+ except Exception:
171
+ return messages
172
+ out = [m for m in messages if not is_baseline_display_message(m)]
173
+ # Preserve identity when nothing was stripped (callers/tests rely on it).
174
+ return messages if len(out) == len(messages) else out
175
+
176
+
158
177
  def apply_lookback_window(
159
178
  messages: list,
160
179
  config: dict | None = None,
@@ -166,6 +185,9 @@ def apply_lookback_window(
166
185
 
167
186
  Full ``messages`` is never mutated. When disabled or small enough, the
168
187
  original list is returned (same object) with meta.truncated=False.
188
+
189
+ Baseline display blobs (soul/gold/welcome) are always stripped from the
190
+ returned API window — they live in the system prompt, not chat turns.
169
191
  """
170
192
  msgs = messages or []
171
193
  on = lookback_enabled(config) if enabled is None else bool(enabled)
@@ -186,9 +208,20 @@ def apply_lookback_window(
186
208
  "start_index": 0,
187
209
  "hidden_messages": 0,
188
210
  }
211
+ def _api_window(window: list, meta: dict[str, Any]) -> tuple[list, dict[str, Any]]:
212
+ """Strip baseline display blobs and refresh window counts."""
213
+ cleaned = _strip_baseline_display(window)
214
+ if cleaned is window:
215
+ return window, meta
216
+ meta = dict(meta)
217
+ meta["window_messages"] = len(cleaned)
218
+ meta["window_user_turns"] = count_user_turns(cleaned)
219
+ meta["baseline_stripped"] = len(window) - len(cleaned)
220
+ return cleaned, meta
221
+
189
222
  if not on or total == 0:
190
223
  _clear_anchor(config)
191
- return msgs, meta
224
+ return _api_window(msgs, meta)
192
225
 
193
226
  # Hysteresis: reuse the previous start (anchor) while its window still
194
227
  # holds n..n+slack user turns. The API prefix then stays append-only
@@ -211,7 +244,7 @@ def apply_lookback_window(
211
244
 
212
245
  if start <= 0:
213
246
  _clear_anchor(config)
214
- return msgs, meta
247
+ return _api_window(msgs, meta)
215
248
 
216
249
  # Cache-aware gate: only truncate when the head we'd hide is meaningfully
217
250
  # bigger than the window we'd keep. Otherwise dropping the front busts the
@@ -242,7 +275,7 @@ def apply_lookback_window(
242
275
  "window_user_turns": count_user_turns(window),
243
276
  "hidden_messages": start,
244
277
  })
245
- return window, meta
278
+ return _api_window(window, meta)
246
279
 
247
280
 
248
281
  def lookback_system_note(meta: dict[str, Any]) -> str:
@@ -57,7 +57,18 @@ from .types import ( # noqa: F401
57
57
  WHAT_NOT_TO_SAVE,
58
58
  )
59
59
  from .consolidator import consolidate_session, mine_files, snapshot_memory_files, new_memory_files # noqa: F401
60
- from .palace import ensure_memory_palace, ensure_short_memory # noqa: F401
60
+ from .palace import ( # noqa: F401
61
+ ensure_memory_palace,
62
+ ensure_short_memory,
63
+ gold_context_messages,
64
+ gold_system_fragment,
65
+ soul_system_fragment,
66
+ is_baseline_display_message,
67
+ is_baseline_memory_name,
68
+ GOLD_MARKER,
69
+ SOUL_MARKER,
70
+ BASELINE_DISPLAY_MARKERS,
71
+ )
61
72
  from .mempalace_bridge import ( # noqa: F401
62
73
  schedule_mempalace_mine,
63
74
  wait_pending_mines,
@@ -13,6 +13,7 @@ from pathlib import Path
13
13
 
14
14
  from .store import (
15
15
  MemoryEntry,
16
+ USER_MEMORY_DIR,
16
17
  get_memory_dir,
17
18
  has_stacked_frontmatter,
18
19
  parse_frontmatter,
@@ -267,6 +268,158 @@ def seed_insight_memories() -> bool:
267
268
  return changed
268
269
 
269
270
 
271
+
272
+ # ── Baseline display markers (GUI/REPL transcript only) ─────────────────────
273
+ # These role:assistant blobs are for the human to see in the chat UI. The model
274
+ # source of truth is gold_system_fragment() / soul_system_fragment() in the
275
+ # system prompt. Agent + lookback strip these markers from the API payload.
276
+ GOLD_MARKER = "[Golden Memory Loaded:"
277
+ SOUL_MARKER = "[Identity Essence Loaded:"
278
+ SOUL_RELOAD_MARKER = "[Identity Essence Reloaded:"
279
+ WELCOME_MARKER = "<!-- dulus:welcome -->"
280
+ BASELINE_DISPLAY_MARKERS = (
281
+ GOLD_MARKER,
282
+ SOUL_MARKER,
283
+ SOUL_RELOAD_MARKER,
284
+ WELCOME_MARKER,
285
+ )
286
+
287
+
288
+ def is_baseline_display_message(msg: dict | None) -> bool:
289
+ """True if *msg* is a GUI/REPL display blob for soul/gold/welcome."""
290
+ if not isinstance(msg, dict):
291
+ return False
292
+ content = msg.get("content") or ""
293
+ if not isinstance(content, str):
294
+ return False
295
+ s = content.lstrip()
296
+ return any(s.startswith(m) for m in BASELINE_DISPLAY_MARKERS)
297
+
298
+
299
+ def is_baseline_memory_name(name: str | None) -> bool:
300
+ """True for memories that already ride in the system-prompt baseline.
301
+
302
+ MemPalace per-turn inject must skip these — re-injecting a truncated
303
+ ``short_memory`` on top of the full system copy is pure noise.
304
+ """
305
+ if not name:
306
+ return False
307
+ key = str(name).strip().lower().replace("-", "_").replace(" ", "_")
308
+ key = key.rsplit("/", 1)[-1].rsplit("\\", 1)[-1].rsplit(".", 1)[0]
309
+ if key in {"short_memory", "shortmemory", "soul"}:
310
+ return True
311
+ if key.startswith("golden_memory") or "short_memory" in key:
312
+ return True
313
+ return False
314
+
315
+
316
+ def _clamp_body(body: str, max_chars: int) -> str:
317
+ body = (body or "").strip()
318
+ if not body or len(body) <= max_chars:
319
+ return body
320
+ head = max_chars * 3 // 4
321
+ return (
322
+ body[:head]
323
+ + "\n\n[... gold memory clipped — file is oversized; "
324
+ "re-curate it with MemorySave ...]\n\n"
325
+ + body[-(max_chars - head):]
326
+ )
327
+
328
+
329
+ def _iter_gold_entries(max_chars: int = 8000):
330
+ """Yield (name, body) for every gold memory, short_memory first."""
331
+ try:
332
+ ensure_short_memory(force_gold=True)
333
+ except Exception:
334
+ pass
335
+ try:
336
+ from .store import load_index
337
+ entries = [e for e in load_index("all") if getattr(e, "gold", False)]
338
+ except Exception:
339
+ return
340
+ def _sort_key(e):
341
+ name = (getattr(e, "name", "") or "").lower()
342
+ return (0 if name == "short_memory" else 1, name)
343
+
344
+ for entry in sorted(entries, key=_sort_key):
345
+ body = _clamp_body(getattr(entry, "content", "") or "", max_chars)
346
+ if not body:
347
+ continue
348
+ yield (getattr(entry, "name", "gold") or "gold", body)
349
+
350
+
351
+ def gold_system_fragment(max_chars: int = 8000, max_total: int = 12000) -> str:
352
+ """Gold memories as a stable system-prompt block (model source of truth).
353
+
354
+ This is how the model actually *receives* short_memory and other gold
355
+ entries — not as ``role:assistant`` chat turns (those break web-history
356
+ consolidate, Anthropic role alternation, and lookback).
357
+
358
+ Deliberately NOT gated by ``mem_palace``. Called every turn from
359
+ ``build_system_prompt`` so edits to short_memory.md show up immediately.
360
+ """
361
+ parts: list[str] = []
362
+ total = 0
363
+ for name, body in _iter_gold_entries(max_chars=max_chars):
364
+ block = f"[Golden Memory Loaded: {name}]\n\n{body}"
365
+ if total and total + len(block) > max_total:
366
+ break
367
+ parts.append(block)
368
+ total += len(block) + 2
369
+ if not parts:
370
+ return ""
371
+ return (
372
+ "# ── Golden Memory (always-on baseline — NOT gated by /mem_palace) ──\n"
373
+ "These are curated essentials. Treat short_memory as the live scratchpad "
374
+ "of where we left off; do not ask the user to re-explain it.\n\n"
375
+ + "\n\n".join(parts)
376
+ )
377
+
378
+
379
+ def soul_system_fragment(max_chars: int = 8000) -> str:
380
+ """Soul identity as a system-prompt block (same rationale as gold)."""
381
+ try:
382
+ path = USER_MEMORY_DIR / "soul.md"
383
+ if not path.exists():
384
+ return ""
385
+ raw = path.read_text(encoding="utf-8", errors="replace")
386
+ body = raw
387
+ if raw.lstrip().startswith("---"):
388
+ try:
389
+ from .store import parse_frontmatter
390
+ _, body = parse_frontmatter(raw)
391
+ except Exception:
392
+ body = raw
393
+ body = _clamp_body(body, max_chars)
394
+ if not body:
395
+ return ""
396
+ return (
397
+ "# ── Identity Essence (soul) ──\n"
398
+ f"[Identity Essence Loaded: soul]\n\n{body}"
399
+ )
400
+ except Exception:
401
+ return ""
402
+
403
+
404
+ def gold_context_messages(max_chars: int = 8000) -> list[dict[str, str]]:
405
+ """GUI/REPL *display* copies of gold memories (NOT the model source of truth).
406
+
407
+ Kept as ``role:assistant`` so `/api/chat/history` and the desktop GUI can
408
+ render "🏆 Gold memory loaded" in the transcript. The agent loop strips
409
+ these markers before the provider call; the real baseline rides in the
410
+ system prompt via ``gold_system_fragment()``.
411
+
412
+ Deliberately NOT gated by the ``mem_palace`` config toggle.
413
+ """
414
+ messages: list[dict[str, str]] = []
415
+ for name, body in _iter_gold_entries(max_chars=max_chars):
416
+ messages.append({
417
+ "role": "assistant",
418
+ "content": f"[Golden Memory Loaded: {name}]\n\n{body}",
419
+ })
420
+ return messages
421
+
422
+
270
423
  def ensure_memory_palace() -> bool:
271
424
  """Initialize missing core buckets + always ensure gold short_memory.
272
425
 
@@ -10,7 +10,7 @@ build-backend = "setuptools.build_meta"
10
10
 
11
11
  [project]
12
12
  name = "dulus"
13
- version = "3.13.4"
13
+ version = "3.13.6"
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"
@@ -513,27 +513,44 @@ class TestBackgroundTaskManager:
513
513
  assert path.exists()
514
514
 
515
515
  def test_launch_worker_creates_subprocess(self, tmp_manager: BackgroundTaskManager) -> None:
516
- # Actually test subprocess creation (will create a real process)
517
- view = tmp_manager.create_bash_task("echo 'HELLO_FROM_TEST'", "test echo")
516
+ # Actually test subprocess creation (will create a real process).
517
+ # No single quotes — Windows cmd treats them literally; the worker
518
+ # runner now uses repr()-safe Python, but keep the command portable.
519
+ view = tmp_manager.create_bash_task("echo HELLO_FROM_TEST", "test echo")
518
520
  assert view.runtime.worker_pid is not None
519
521
  assert view.runtime.worker_pid > 0
520
522
 
521
- # Wait a bit for the process to complete
522
- time.sleep(0.5)
523
-
524
- # Check output
525
- chunk = tmp_manager.read_output(view.spec.id)
526
- assert "HELLO_FROM_TEST" in chunk.text
523
+ # Poll — CI Windows runners are slower than a fixed 0.5s sleep.
524
+ deadline = time.time() + 10.0
525
+ text = ""
526
+ while time.time() < deadline:
527
+ chunk = tmp_manager.read_output(view.spec.id)
528
+ text = chunk.text or ""
529
+ if "HELLO_FROM_TEST" in text:
530
+ break
531
+ time.sleep(0.1)
532
+ assert "HELLO_FROM_TEST" in text, f"worker output empty/missing marker: {text!r}"
527
533
 
528
534
  def test_launch_worker_with_cwd(self, tmp_manager: BackgroundTaskManager, tmp_path: Path) -> None:
529
535
  subdir = tmp_path / "subdir"
530
536
  subdir.mkdir()
531
537
 
532
- view = tmp_manager.create_bash_task("pwd", "test cwd", cwd=str(subdir))
533
- time.sleep(0.5)
534
-
535
- chunk = tmp_manager.read_output(view.spec.id)
536
- assert str(subdir) in chunk.text
538
+ # Portable cwd probe: Python prints the path on every OS (pwd is Unix-only).
539
+ view = tmp_manager.create_bash_task(
540
+ f'"{sys.executable}" -c "import os; print(os.getcwd())"',
541
+ "test cwd",
542
+ cwd=str(subdir),
543
+ )
544
+ deadline = time.time() + 10.0
545
+ text = ""
546
+ while time.time() < deadline:
547
+ chunk = tmp_manager.read_output(view.spec.id)
548
+ text = chunk.text or ""
549
+ if str(subdir) in text or subdir.name in text:
550
+ break
551
+ time.sleep(0.1)
552
+ # Normalize separators so Win paths still match.
553
+ assert str(subdir) in text or str(subdir).replace("\\", "/") in text.replace("\\", "/")
537
554
 
538
555
  def test_recovery_marks_stale_tasks(self, tmp_manager: BackgroundTaskManager) -> None:
539
556
  with patch.object(tmp_manager, "_launch_worker", return_value=12345):