dulus 3.6.0__tar.gz → 3.6.2__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 (330) hide show
  1. {dulus-3.6.0/dulus.egg-info → dulus-3.6.2}/PKG-INFO +2 -2
  2. {dulus-3.6.0 → dulus-3.6.2}/README.md +1 -1
  3. {dulus-3.6.0 → dulus-3.6.2}/analytics.py +21 -1
  4. {dulus-3.6.0 → dulus-3.6.2}/config.py +42 -2
  5. {dulus-3.6.0 → dulus-3.6.2}/docs/CHANGELOG.md +15 -0
  6. {dulus-3.6.0 → dulus-3.6.2}/docs/news.md +4 -0
  7. {dulus-3.6.0 → dulus-3.6.2/dulus.egg-info}/PKG-INFO +2 -2
  8. {dulus-3.6.0 → dulus-3.6.2}/dulus.py +21 -0
  9. {dulus-3.6.0 → dulus-3.6.2}/memory/sessions.py +44 -12
  10. {dulus-3.6.0 → dulus-3.6.2}/memory/tools.py +22 -8
  11. {dulus-3.6.0 → dulus-3.6.2}/pyproject.toml +1 -1
  12. {dulus-3.6.0 → dulus-3.6.2}/LICENSE +0 -0
  13. {dulus-3.6.0 → dulus-3.6.2}/MANIFEST.in +0 -0
  14. {dulus-3.6.0 → dulus-3.6.2}/agent.py +0 -0
  15. {dulus-3.6.0 → dulus-3.6.2}/backend/__init__.py +0 -0
  16. {dulus-3.6.0 → dulus-3.6.2}/backend/agents_bridge.py +0 -0
  17. {dulus-3.6.0 → dulus-3.6.2}/backend/compressor.py +0 -0
  18. {dulus-3.6.0 → dulus-3.6.2}/backend/context.py +0 -0
  19. {dulus-3.6.0 → dulus-3.6.2}/backend/githook.py +0 -0
  20. {dulus-3.6.0 → dulus-3.6.2}/backend/marketplace.py +0 -0
  21. {dulus-3.6.0 → dulus-3.6.2}/backend/mempalace_bridge.py +0 -0
  22. {dulus-3.6.0 → dulus-3.6.2}/backend/personas.py +0 -0
  23. {dulus-3.6.0 → dulus-3.6.2}/backend/plugins.py +0 -0
  24. {dulus-3.6.0 → dulus-3.6.2}/backend/server.py +0 -0
  25. {dulus-3.6.0 → dulus-3.6.2}/backend/tasks.py +0 -0
  26. {dulus-3.6.0 → dulus-3.6.2}/batch_api.py +0 -0
  27. {dulus-3.6.0 → dulus-3.6.2}/checkpoint/__init__.py +0 -0
  28. {dulus-3.6.0 → dulus-3.6.2}/checkpoint/hooks.py +0 -0
  29. {dulus-3.6.0 → dulus-3.6.2}/checkpoint/store.py +0 -0
  30. {dulus-3.6.0 → dulus-3.6.2}/checkpoint/types.py +0 -0
  31. {dulus-3.6.0 → dulus-3.6.2}/claude_code_watcher.py +0 -0
  32. {dulus-3.6.0 → dulus-3.6.2}/clipboard_utils.py +0 -0
  33. {dulus-3.6.0 → dulus-3.6.2}/cloudsave.py +0 -0
  34. {dulus-3.6.0 → dulus-3.6.2}/common.py +0 -0
  35. {dulus-3.6.0 → dulus-3.6.2}/compaction.py +0 -0
  36. {dulus-3.6.0 → dulus-3.6.2}/context.py +0 -0
  37. {dulus-3.6.0 → dulus-3.6.2}/data/__init__.py +0 -0
  38. {dulus-3.6.0 → dulus-3.6.2}/data/active_persona.json +0 -0
  39. {dulus-3.6.0 → dulus-3.6.2}/data/marketplace.json +0 -0
  40. {dulus-3.6.0 → dulus-3.6.2}/data/personas.json +0 -0
  41. {dulus-3.6.0 → dulus-3.6.2}/data/plugins/__init__.py +0 -0
  42. {dulus-3.6.0 → dulus-3.6.2}/data/plugins/composio/__init__.py +0 -0
  43. {dulus-3.6.0 → dulus-3.6.2}/data/plugins/composio/composio_plugin/__init__.py +0 -0
  44. {dulus-3.6.0 → dulus-3.6.2}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
  45. {dulus-3.6.0 → dulus-3.6.2}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
  46. {dulus-3.6.0 → dulus-3.6.2}/data/plugins/composio/plugin.json +0 -0
  47. {dulus-3.6.0 → dulus-3.6.2}/data/plugins/composio/plugin_tool.py +0 -0
  48. {dulus-3.6.0 → dulus-3.6.2}/data/tasks.json +0 -0
  49. {dulus-3.6.0 → dulus-3.6.2}/docs/API.md +0 -0
  50. {dulus-3.6.0 → dulus-3.6.2}/docs/BRAND_GUIDE.md +0 -0
  51. {dulus-3.6.0 → dulus-3.6.2}/docs/CONTRIBUTING.md +0 -0
  52. {dulus-3.6.0 → dulus-3.6.2}/docs/DEPLOYMENT.md +0 -0
  53. {dulus-3.6.0 → dulus-3.6.2}/docs/Dulus_AI_Whitepaper_(v2.0).pdf +0 -0
  54. {dulus-3.6.0 → dulus-3.6.2}/docs/FAQ.md +0 -0
  55. {dulus-3.6.0 → dulus-3.6.2}/docs/GETTING_STARTED.md +0 -0
  56. {dulus-3.6.0 → dulus-3.6.2}/docs/INVESTOR_BRIEF.md +0 -0
  57. {dulus-3.6.0 → dulus-3.6.2}/docs/README.md +0 -0
  58. {dulus-3.6.0 → dulus-3.6.2}/docs/README_AR.md +0 -0
  59. {dulus-3.6.0 → dulus-3.6.2}/docs/README_EN.md +0 -0
  60. {dulus-3.6.0 → dulus-3.6.2}/docs/README_ES.md +0 -0
  61. {dulus-3.6.0 → dulus-3.6.2}/docs/README_FR.md +0 -0
  62. {dulus-3.6.0 → dulus-3.6.2}/docs/README_JA.md +0 -0
  63. {dulus-3.6.0 → dulus-3.6.2}/docs/README_KO.md +0 -0
  64. {dulus-3.6.0 → dulus-3.6.2}/docs/README_PT.md +0 -0
  65. {dulus-3.6.0 → dulus-3.6.2}/docs/README_RU.md +0 -0
  66. {dulus-3.6.0 → dulus-3.6.2}/docs/README_ZH.md +0 -0
  67. {dulus-3.6.0 → dulus-3.6.2}/docs/SECURITY.md +0 -0
  68. {dulus-3.6.0 → dulus-3.6.2}/docs/__init__.py +0 -0
  69. {dulus-3.6.0 → dulus-3.6.2}/docs/api.html +0 -0
  70. {dulus-3.6.0 → dulus-3.6.2}/docs/architecture.md +0 -0
  71. {dulus-3.6.0 → dulus-3.6.2}/docs/azure-speech-template.json +0 -0
  72. {dulus-3.6.0 → dulus-3.6.2}/docs/dashboard/index.html +0 -0
  73. {dulus-3.6.0 → dulus-3.6.2}/docs/divider.svg +0 -0
  74. {dulus-3.6.0 → dulus-3.6.2}/docs/dulus-bird.png +0 -0
  75. {dulus-3.6.0 → dulus-3.6.2}/docs/generate.py +0 -0
  76. {dulus-3.6.0 → dulus-3.6.2}/docs/hero.svg +0 -0
  77. {dulus-3.6.0 → dulus-3.6.2}/docs/index.html +0 -0
  78. {dulus-3.6.0 → dulus-3.6.2}/docs/nvidia-models.svg +0 -0
  79. {dulus-3.6.0 → dulus-3.6.2}/docs/particle-playground.html +0 -0
  80. {dulus-3.6.0 → dulus-3.6.2}/docs/personas/index.html +0 -0
  81. {dulus-3.6.0 → dulus-3.6.2}/docs/poetry-banner.png +0 -0
  82. {dulus-3.6.0 → dulus-3.6.2}/docs/preview.html +0 -0
  83. {dulus-3.6.0 → dulus-3.6.2}/docs/sec-agents.svg +0 -0
  84. {dulus-3.6.0 → dulus-3.6.2}/docs/sec-brainstorm.svg +0 -0
  85. {dulus-3.6.0 → dulus-3.6.2}/docs/sec-bridges.svg +0 -0
  86. {dulus-3.6.0 → dulus-3.6.2}/docs/sec-features.svg +0 -0
  87. {dulus-3.6.0 → dulus-3.6.2}/docs/sec-freetier.svg +0 -0
  88. {dulus-3.6.0 → dulus-3.6.2}/docs/sec-memory.svg +0 -0
  89. {dulus-3.6.0 → dulus-3.6.2}/docs/sec-models.svg +0 -0
  90. {dulus-3.6.0 → dulus-3.6.2}/docs/sec-perms.svg +0 -0
  91. {dulus-3.6.0 → dulus-3.6.2}/docs/sec-plugins.svg +0 -0
  92. {dulus-3.6.0 → dulus-3.6.2}/docs/sec-quickstart.svg +0 -0
  93. {dulus-3.6.0 → dulus-3.6.2}/docs/sec-ssj.svg +0 -0
  94. {dulus-3.6.0 → dulus-3.6.2}/docs/spinners.svg +0 -0
  95. {dulus-3.6.0 → dulus-3.6.2}/docs/split-pane.svg +0 -0
  96. {dulus-3.6.0 → dulus-3.6.2}/docs/terminal-boot.svg +0 -0
  97. {dulus-3.6.0 → dulus-3.6.2}/dulus.egg-info/SOURCES.txt +0 -0
  98. {dulus-3.6.0 → dulus-3.6.2}/dulus.egg-info/dependency_links.txt +0 -0
  99. {dulus-3.6.0 → dulus-3.6.2}/dulus.egg-info/entry_points.txt +0 -0
  100. {dulus-3.6.0 → dulus-3.6.2}/dulus.egg-info/requires.txt +0 -0
  101. {dulus-3.6.0 → dulus-3.6.2}/dulus.egg-info/top_level.txt +0 -0
  102. {dulus-3.6.0 → dulus-3.6.2}/dulus_gui.py +0 -0
  103. {dulus-3.6.0 → dulus-3.6.2}/dulus_gui_web.py +0 -0
  104. {dulus-3.6.0 → dulus-3.6.2}/dulus_mcp/__init__.py +0 -0
  105. {dulus-3.6.0 → dulus-3.6.2}/dulus_mcp/client.py +0 -0
  106. {dulus-3.6.0 → dulus-3.6.2}/dulus_mcp/config.py +0 -0
  107. {dulus-3.6.0 → dulus-3.6.2}/dulus_mcp/tools.py +0 -0
  108. {dulus-3.6.0 → dulus-3.6.2}/dulus_mcp/types.py +0 -0
  109. {dulus-3.6.0 → dulus-3.6.2}/dulus_tools/__init__.py +0 -0
  110. {dulus-3.6.0 → dulus-3.6.2}/dulus_tools/add_dir_manager.py +0 -0
  111. {dulus-3.6.0 → dulus-3.6.2}/dulus_tools/afk_mode.py +0 -0
  112. {dulus-3.6.0 → dulus-3.6.2}/dulus_tools/approval_runtime.py +0 -0
  113. {dulus-3.6.0 → dulus-3.6.2}/dulus_tools/background_tasks.py +0 -0
  114. {dulus-3.6.0 → dulus-3.6.2}/dulus_tools/clipboard_utils.py +0 -0
  115. {dulus-3.6.0 → dulus-3.6.2}/dulus_tools/diff_visualizer.py +0 -0
  116. {dulus-3.6.0 → dulus-3.6.2}/dulus_tools/export_import.py +0 -0
  117. {dulus-3.6.0 → dulus-3.6.2}/dulus_tools/hook_engine.py +0 -0
  118. {dulus-3.6.0 → dulus-3.6.2}/dulus_tools/notification_manager.py +0 -0
  119. {dulus-3.6.0 → dulus-3.6.2}/dulus_tools/session_fork.py +0 -0
  120. {dulus-3.6.0 → dulus-3.6.2}/dulus_tools/shell_mode.py +0 -0
  121. {dulus-3.6.0 → dulus-3.6.2}/dulus_tools/todo_visualizer.py +0 -0
  122. {dulus-3.6.0 → dulus-3.6.2}/dulus_tools/wire_events.py +0 -0
  123. {dulus-3.6.0 → dulus-3.6.2}/dulus_tools/yolo_mode.py +0 -0
  124. {dulus-3.6.0 → dulus-3.6.2}/governance.py +0 -0
  125. {dulus-3.6.0 → dulus-3.6.2}/gui/__init__.py +0 -0
  126. {dulus-3.6.0 → dulus-3.6.2}/gui/agent_bridge.py +0 -0
  127. {dulus-3.6.0 → dulus-3.6.2}/gui/chat_widget.py +0 -0
  128. {dulus-3.6.0 → dulus-3.6.2}/gui/dulus_orchestrator/App.tsx +0 -0
  129. {dulus-3.6.0 → dulus-3.6.2}/gui/dulus_orchestrator/THIRD_PARTY_NOTICES.md +0 -0
  130. {dulus-3.6.0 → dulus-3.6.2}/gui/dulus_orchestrator/dist/index.html +0 -0
  131. {dulus-3.6.0 → dulus-3.6.2}/gui/dulus_orchestrator/dist/standalone.html +0 -0
  132. {dulus-3.6.0 → dulus-3.6.2}/gui/dulus_orchestrator/index.html +0 -0
  133. {dulus-3.6.0 → dulus-3.6.2}/gui/dulus_orchestrator/index.tsx +0 -0
  134. {dulus-3.6.0 → dulus-3.6.2}/gui/dulus_orchestrator/metadata.json +0 -0
  135. {dulus-3.6.0 → dulus-3.6.2}/gui/dulus_orchestrator/package-lock.json +0 -0
  136. {dulus-3.6.0 → dulus-3.6.2}/gui/dulus_orchestrator/package.json +0 -0
  137. {dulus-3.6.0 → dulus-3.6.2}/gui/dulus_orchestrator/types.ts +0 -0
  138. {dulus-3.6.0 → dulus-3.6.2}/gui/dulus_orchestrator/utils.ts +0 -0
  139. {dulus-3.6.0 → dulus-3.6.2}/gui/dulus_orchestrator/vertex-ai-proxy-interceptor.js +0 -0
  140. {dulus-3.6.0 → dulus-3.6.2}/gui/dulus_orchestrator/vite.config.ts +0 -0
  141. {dulus-3.6.0 → dulus-3.6.2}/gui/main_window.py +0 -0
  142. {dulus-3.6.0 → dulus-3.6.2}/gui/personas.py +0 -0
  143. {dulus-3.6.0 → dulus-3.6.2}/gui/sandbox_server.py +0 -0
  144. {dulus-3.6.0 → dulus-3.6.2}/gui/session_utils.py +0 -0
  145. {dulus-3.6.0 → dulus-3.6.2}/gui/settings_dialog.py +0 -0
  146. {dulus-3.6.0 → dulus-3.6.2}/gui/sidebar.py +0 -0
  147. {dulus-3.6.0 → dulus-3.6.2}/gui/tasks_view.py +0 -0
  148. {dulus-3.6.0 → dulus-3.6.2}/gui/themes.py +0 -0
  149. {dulus-3.6.0 → dulus-3.6.2}/gui/tool_panel.py +0 -0
  150. {dulus-3.6.0 → dulus-3.6.2}/input.py +0 -0
  151. {dulus-3.6.0 → dulus-3.6.2}/license_manager.py +0 -0
  152. {dulus-3.6.0 → dulus-3.6.2}/memory/__init__.py +0 -0
  153. {dulus-3.6.0 → dulus-3.6.2}/memory/audit.py +0 -0
  154. {dulus-3.6.0 → dulus-3.6.2}/memory/consolidator.py +0 -0
  155. {dulus-3.6.0 → dulus-3.6.2}/memory/context.py +0 -0
  156. {dulus-3.6.0 → dulus-3.6.2}/memory/offload.py +0 -0
  157. {dulus-3.6.0 → dulus-3.6.2}/memory/palace.py +0 -0
  158. {dulus-3.6.0 → dulus-3.6.2}/memory/scan.py +0 -0
  159. {dulus-3.6.0 → dulus-3.6.2}/memory/store.py +0 -0
  160. {dulus-3.6.0 → dulus-3.6.2}/memory/types.py +0 -0
  161. {dulus-3.6.0 → dulus-3.6.2}/memory/vector_search.py +0 -0
  162. {dulus-3.6.0 → dulus-3.6.2}/multi_agent/__init__.py +0 -0
  163. {dulus-3.6.0 → dulus-3.6.2}/multi_agent/subagent.py +0 -0
  164. {dulus-3.6.0 → dulus-3.6.2}/multi_agent/tools.py +0 -0
  165. {dulus-3.6.0 → dulus-3.6.2}/offload_helper.py +0 -0
  166. {dulus-3.6.0 → dulus-3.6.2}/personality_config.py +0 -0
  167. {dulus-3.6.0 → dulus-3.6.2}/plugin/__init__.py +0 -0
  168. {dulus-3.6.0 → dulus-3.6.2}/plugin/autoadapter.py +0 -0
  169. {dulus-3.6.0 → dulus-3.6.2}/plugin/loader.py +0 -0
  170. {dulus-3.6.0 → dulus-3.6.2}/plugin/recommend.py +0 -0
  171. {dulus-3.6.0 → dulus-3.6.2}/plugin/store.py +0 -0
  172. {dulus-3.6.0 → dulus-3.6.2}/plugin/types.py +0 -0
  173. {dulus-3.6.0 → dulus-3.6.2}/providers.py +0 -0
  174. {dulus-3.6.0 → dulus-3.6.2}/sandbox/README.md +0 -0
  175. {dulus-3.6.0 → dulus-3.6.2}/sandbox/__init__.py +0 -0
  176. {dulus-3.6.0 → dulus-3.6.2}/sandbox/components.json +0 -0
  177. {dulus-3.6.0 → dulus-3.6.2}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
  178. {dulus-3.6.0 → dulus-3.6.2}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
  179. {dulus-3.6.0 → dulus-3.6.2}/sandbox/dist/index.html +0 -0
  180. {dulus-3.6.0 → dulus-3.6.2}/sandbox/dist/wallpaper-default.jpg +0 -0
  181. {dulus-3.6.0 → dulus-3.6.2}/sandbox/dist/wallpapers/default.jpeg +0 -0
  182. {dulus-3.6.0 → dulus-3.6.2}/sandbox/dist/wallpapers/light.jpeg +0 -0
  183. {dulus-3.6.0 → dulus-3.6.2}/sandbox/dist/wallpapers/nature.jpeg +0 -0
  184. {dulus-3.6.0 → dulus-3.6.2}/sandbox/dist/wallpapers/tech.jpeg +0 -0
  185. {dulus-3.6.0 → dulus-3.6.2}/sandbox/eslint.config.js +0 -0
  186. {dulus-3.6.0 → dulus-3.6.2}/sandbox/index.html +0 -0
  187. {dulus-3.6.0 → dulus-3.6.2}/sandbox/info.md +0 -0
  188. {dulus-3.6.0 → dulus-3.6.2}/sandbox/package-lock.json +0 -0
  189. {dulus-3.6.0 → dulus-3.6.2}/sandbox/package.json +0 -0
  190. {dulus-3.6.0 → dulus-3.6.2}/sandbox/postcss.config.js +0 -0
  191. {dulus-3.6.0 → dulus-3.6.2}/sandbox/tailwind.config.js +0 -0
  192. {dulus-3.6.0 → dulus-3.6.2}/sandbox/tsconfig.app.json +0 -0
  193. {dulus-3.6.0 → dulus-3.6.2}/sandbox/tsconfig.json +0 -0
  194. {dulus-3.6.0 → dulus-3.6.2}/sandbox/tsconfig.node.json +0 -0
  195. {dulus-3.6.0 → dulus-3.6.2}/sandbox/vite.config.ts +0 -0
  196. {dulus-3.6.0 → dulus-3.6.2}/sandbox_bootstrap.py +0 -0
  197. {dulus-3.6.0 → dulus-3.6.2}/setup.cfg +0 -0
  198. {dulus-3.6.0 → dulus-3.6.2}/skill/__init__.py +0 -0
  199. {dulus-3.6.0 → dulus-3.6.2}/skill/builtin.py +0 -0
  200. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/LICENSE-obsidian-skills +0 -0
  201. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/README.md +0 -0
  202. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/defuddle/SKILL.md +0 -0
  203. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/json-canvas/SKILL.md +0 -0
  204. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/json-canvas/references/EXAMPLES.md +0 -0
  205. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/obsidian-bases/SKILL.md +0 -0
  206. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/obsidian-bases/references/FUNCTIONS_REFERENCE.md +0 -0
  207. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/obsidian-cli/SKILL.md +0 -0
  208. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/obsidian-markdown/SKILL.md +0 -0
  209. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/obsidian-markdown/references/CALLOUTS.md +0 -0
  210. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/obsidian-markdown/references/EMBEDS.md +0 -0
  211. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/obsidian-markdown/references/PROPERTIES.md +0 -0
  212. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/README.md +0 -0
  213. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/SKILL.md +0 -0
  214. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/addresses.json +0 -0
  215. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/advancedbackgroundchecks.json +0 -0
  216. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/beenverified.json +0 -0
  217. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/clustal.json +0 -0
  218. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/clustrmaps.json +0 -0
  219. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/cyberbackgroundchecks.json +0 -0
  220. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/familytreenow.json +0 -0
  221. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/fastpeoplesearch.json +0 -0
  222. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/intelius.json +0 -0
  223. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/mylife.json +0 -0
  224. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/nuwber.json +0 -0
  225. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/peekyou.json +0 -0
  226. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/peoplefinders.json +0 -0
  227. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/radaris.json +0 -0
  228. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/rehold.json +0 -0
  229. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/searchpeoplefree.json +0 -0
  230. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/socialcatfish.json +0 -0
  231. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/spokeo.json +0 -0
  232. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/thatsthem.json +0 -0
  233. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/truepeoplesearch.json +0 -0
  234. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/usphonebook.json +0 -0
  235. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/whitepages.json +0 -0
  236. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/legal/ccpa.md +0 -0
  237. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/legal/drop.md +0 -0
  238. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/legal/gdpr.md +0 -0
  239. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/methods.md +0 -0
  240. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/site-playbooks.md +0 -0
  241. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/references/state-machine.md +0 -0
  242. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/autopilot.py +0 -0
  243. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/badbool.py +0 -0
  244. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/brokers.py +0 -0
  245. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/cdp.py +0 -0
  246. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/config.py +0 -0
  247. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/crypto.py +0 -0
  248. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/dossier.py +0 -0
  249. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/email_modes.py +0 -0
  250. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/emailer.py +0 -0
  251. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/ledger.py +0 -0
  252. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/legal.py +0 -0
  253. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/paths.py +0 -0
  254. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/pdd.py +0 -0
  255. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/registry.py +0 -0
  256. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/report.py +0 -0
  257. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/scan.py +0 -0
  258. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/storage.py +0 -0
  259. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/tiers.py +0 -0
  260. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/vectors.py +0 -0
  261. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/templates/consent/authorization.md +0 -0
  262. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/templates/emails/ccpa-authorized-agent.txt +0 -0
  263. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/templates/emails/ccpa-deletion.txt +0 -0
  264. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/templates/emails/ccpa-indirect-deletion.txt +0 -0
  265. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/templates/emails/gdpr-erasure.txt +0 -0
  266. {dulus-3.6.0 → dulus-3.6.2}/skill/bundled/unbroker/templates/emails/generic-optout.txt +0 -0
  267. {dulus-3.6.0 → dulus-3.6.2}/skill/clawhub.py +0 -0
  268. {dulus-3.6.0 → dulus-3.6.2}/skill/executor.py +0 -0
  269. {dulus-3.6.0 → dulus-3.6.2}/skill/loader.py +0 -0
  270. {dulus-3.6.0 → dulus-3.6.2}/skill/tools.py +0 -0
  271. {dulus-3.6.0 → dulus-3.6.2}/skills.py +0 -0
  272. {dulus-3.6.0 → dulus-3.6.2}/soul.py +0 -0
  273. {dulus-3.6.0 → dulus-3.6.2}/spinner.py +0 -0
  274. {dulus-3.6.0 → dulus-3.6.2}/string_utils.py +0 -0
  275. {dulus-3.6.0 → dulus-3.6.2}/subagent.py +0 -0
  276. {dulus-3.6.0 → dulus-3.6.2}/task/__init__.py +0 -0
  277. {dulus-3.6.0 → dulus-3.6.2}/task/store.py +0 -0
  278. {dulus-3.6.0 → dulus-3.6.2}/task/tools.py +0 -0
  279. {dulus-3.6.0 → dulus-3.6.2}/task/types.py +0 -0
  280. {dulus-3.6.0 → dulus-3.6.2}/tests/test_afk_yolo.py +0 -0
  281. {dulus-3.6.0 → dulus-3.6.2}/tests/test_approval_runtime.py +0 -0
  282. {dulus-3.6.0 → dulus-3.6.2}/tests/test_background_task_tools.py +0 -0
  283. {dulus-3.6.0 → dulus-3.6.2}/tests/test_background_tasks.py +0 -0
  284. {dulus-3.6.0 → dulus-3.6.2}/tests/test_checkpoint.py +0 -0
  285. {dulus-3.6.0 → dulus-3.6.2}/tests/test_clipboard_utils.py +0 -0
  286. {dulus-3.6.0 → dulus-3.6.2}/tests/test_compaction.py +0 -0
  287. {dulus-3.6.0 → dulus-3.6.2}/tests/test_diff_view.py +0 -0
  288. {dulus-3.6.0 → dulus-3.6.2}/tests/test_diff_visualization.py +0 -0
  289. {dulus-3.6.0 → dulus-3.6.2}/tests/test_display_blocks.py +0 -0
  290. {dulus-3.6.0 → dulus-3.6.2}/tests/test_export_import.py +0 -0
  291. {dulus-3.6.0 → dulus-3.6.2}/tests/test_hook_engine.py +0 -0
  292. {dulus-3.6.0 → dulus-3.6.2}/tests/test_injection_fix.py +0 -0
  293. {dulus-3.6.0 → dulus-3.6.2}/tests/test_license.py +0 -0
  294. {dulus-3.6.0 → dulus-3.6.2}/tests/test_mcp.py +0 -0
  295. {dulus-3.6.0 → dulus-3.6.2}/tests/test_memory.py +0 -0
  296. {dulus-3.6.0 → dulus-3.6.2}/tests/test_notification_manager.py +0 -0
  297. {dulus-3.6.0 → dulus-3.6.2}/tests/test_plugin.py +0 -0
  298. {dulus-3.6.0 → dulus-3.6.2}/tests/test_session_fork.py +0 -0
  299. {dulus-3.6.0 → dulus-3.6.2}/tests/test_shell_mode.py +0 -0
  300. {dulus-3.6.0 → dulus-3.6.2}/tests/test_skills.py +0 -0
  301. {dulus-3.6.0 → dulus-3.6.2}/tests/test_steer_input.py +0 -0
  302. {dulus-3.6.0 → dulus-3.6.2}/tests/test_subagent.py +0 -0
  303. {dulus-3.6.0 → dulus-3.6.2}/tests/test_task.py +0 -0
  304. {dulus-3.6.0 → dulus-3.6.2}/tests/test_telegram_buffer.py +0 -0
  305. {dulus-3.6.0 → dulus-3.6.2}/tests/test_think_tool.py +0 -0
  306. {dulus-3.6.0 → dulus-3.6.2}/tests/test_todo_tool.py +0 -0
  307. {dulus-3.6.0 → dulus-3.6.2}/tests/test_todo_visualization.py +0 -0
  308. {dulus-3.6.0 → dulus-3.6.2}/tests/test_tool_registry.py +0 -0
  309. {dulus-3.6.0 → dulus-3.6.2}/tests/test_voice.py +0 -0
  310. {dulus-3.6.0 → dulus-3.6.2}/tests/test_wire_events.py +0 -0
  311. {dulus-3.6.0 → dulus-3.6.2}/tmux_offloader.py +0 -0
  312. {dulus-3.6.0 → dulus-3.6.2}/tmux_tools.py +0 -0
  313. {dulus-3.6.0 → dulus-3.6.2}/tool_registry.py +0 -0
  314. {dulus-3.6.0 → dulus-3.6.2}/tools.py +0 -0
  315. {dulus-3.6.0 → dulus-3.6.2}/ui/__init__.py +0 -0
  316. {dulus-3.6.0 → dulus-3.6.2}/ui/input.py +0 -0
  317. {dulus-3.6.0 → dulus-3.6.2}/ui/render.py +0 -0
  318. {dulus-3.6.0 → dulus-3.6.2}/voice/__init__.py +0 -0
  319. {dulus-3.6.0 → dulus-3.6.2}/voice/audio_utils.py +0 -0
  320. {dulus-3.6.0 → dulus-3.6.2}/voice/keyterms.py +0 -0
  321. {dulus-3.6.0 → dulus-3.6.2}/voice/recorder.py +0 -0
  322. {dulus-3.6.0 → dulus-3.6.2}/voice/stt.py +0 -0
  323. {dulus-3.6.0 → dulus-3.6.2}/voice/tts.py +0 -0
  324. {dulus-3.6.0 → dulus-3.6.2}/voice/wake_word.py +0 -0
  325. {dulus-3.6.0 → dulus-3.6.2}/webbridge/__init__.py +0 -0
  326. {dulus-3.6.0 → dulus-3.6.2}/webbridge/core.py +0 -0
  327. {dulus-3.6.0 → dulus-3.6.2}/webbridge/tools.py +0 -0
  328. {dulus-3.6.0 → dulus-3.6.2}/webchat.py +0 -0
  329. {dulus-3.6.0 → dulus-3.6.2}/webchat_server.py +0 -0
  330. {dulus-3.6.0 → dulus-3.6.2}/welcome.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dulus
3
- Version: 3.6.0
3
+ Version: 3.6.2
4
4
  Summary: Use IA as an agent without an API key. $0. Harvest your browser session for Gemini (guest, no login), Claude.ai, Claude Code, Kimi.com, Qwen, DeepSeek — drive them like an API, no per-token billing. Plus LiteLLM (100+ paid providers), local models via Ollama, /lang in 34 languages, Mesa Redonda, voice, OCR, MemPalace, embedded sandbox OS.
5
5
  Author: KevRojo
6
6
  License: GPL-3.0
@@ -133,7 +133,7 @@ SET /sticky_input ON since the first run for the best experience!
133
133
 
134
134
  > Full changelog: [`docs/news.md`](docs/news.md) · Inside the REPL: `/news`
135
135
 
136
- - **📡 dulus.work — announced.** Kevin finally revealed the final piece of the Dulus vision — the one he'd been keeping secret (even from Dulus itself 😂). Every agent ships updates. Nobody ships THIS. Already in motion. Stay tuned. 🦅 → [dulus.work](https://dulus.work)
136
+ - **🌍 dulus.work — THE HUB. The secret is out.** This is what Kevin had been hiding (even from Dulus itself 😂): **a global hub where every Dulus instance on the planet shares what it learns.** Your Dulus finds a fix, polishes a skill, adapts a plugin → it can publish it to the hub → every other Dulus in the world can pull it down. Not an update server an **organism**. Every installation is a node. *The fix a Dulus in Tokyo finds at 3AM, the one in Santo Domingo has by sunrise.* Other agents have N devs improving one product (linear). Dulus makes every user a dev (exponential). The pieces have been quietly shipping for months — SelfImprove engine, Auto-Adapter, the skills format, cloudsave — and now they converge. Domain is live, execution underway. 🦅🇩🇴 → [dulus.work](https://dulus.work)
137
137
 
138
138
  - **Dulus Agent — Telegram communities.** Dulus evolved from a CLI tool into a live AI agent inside Telegram groups. The first fully autonomous AI moderating and conversing in real communities — not a bot, not a filter, a real agent. Groups pay in $DULUS to activate him. We host Dulus for them — that's the business model. The CLI stays free forever. This is the paid layer. 🦅🇩🇴
139
139
 
@@ -68,7 +68,7 @@ SET /sticky_input ON since the first run for the best experience!
68
68
 
69
69
  > Full changelog: [`docs/news.md`](docs/news.md) · Inside the REPL: `/news`
70
70
 
71
- - **📡 dulus.work — announced.** Kevin finally revealed the final piece of the Dulus vision — the one he'd been keeping secret (even from Dulus itself 😂). Every agent ships updates. Nobody ships THIS. Already in motion. Stay tuned. 🦅 → [dulus.work](https://dulus.work)
71
+ - **🌍 dulus.work — THE HUB. The secret is out.** This is what Kevin had been hiding (even from Dulus itself 😂): **a global hub where every Dulus instance on the planet shares what it learns.** Your Dulus finds a fix, polishes a skill, adapts a plugin → it can publish it to the hub → every other Dulus in the world can pull it down. Not an update server an **organism**. Every installation is a node. *The fix a Dulus in Tokyo finds at 3AM, the one in Santo Domingo has by sunrise.* Other agents have N devs improving one product (linear). Dulus makes every user a dev (exponential). The pieces have been quietly shipping for months — SelfImprove engine, Auto-Adapter, the skills format, cloudsave — and now they converge. Domain is live, execution underway. 🦅🇩🇴 → [dulus.work](https://dulus.work)
72
72
 
73
73
  - **Dulus Agent — Telegram communities.** Dulus evolved from a CLI tool into a live AI agent inside Telegram groups. The first fully autonomous AI moderating and conversing in real communities — not a bot, not a filter, a real agent. Groups pay in $DULUS to activate him. We host Dulus for them — that's the business model. The CLI stays free forever. This is the paid layer. 🦅🇩🇴
74
74
 
@@ -41,7 +41,7 @@ import uuid
41
41
  # NOTE: Mixpanel ingestion tokens are write-only and designed to ship in
42
42
  # client code (every website using Mixpanel exposes theirs). It cannot be
43
43
  # used to read any data. Override with DULUS_MP_TOKEN.
44
- MP_TOKEN = os.environ.get("DULUS_MP_TOKEN", "")
44
+ MP_TOKEN = os.environ.get("DULUS_MP_TOKEN", "966ce8f5ccb32f06788f51be9f8bf8f5")
45
45
 
46
46
  _MP_ENDPOINT = "https://api.mixpanel.com/track"
47
47
 
@@ -142,3 +142,23 @@ def track_session_start(config: dict) -> None:
142
142
  "provider": str(config.get("provider", "")),
143
143
  "model": str(config.get("model", "")),
144
144
  })
145
+
146
+
147
+ def track_message_sent(model: str = "") -> None:
148
+ """One event per user prompt sent to the LLM (no content — count only)."""
149
+ track("message_sent", {"model": model})
150
+
151
+
152
+ def track_tool_used(tool_name: str) -> None:
153
+ """One event per tool invocation (tool NAME only — never inputs)."""
154
+ track("tool_used", {"tool": tool_name})
155
+
156
+
157
+ def track_command_used(command: str) -> None:
158
+ """One event per slash command (command NAME only — never args)."""
159
+ track("command_used", {"command": command})
160
+
161
+
162
+ def track_model_selected(model: str, provider: str = "") -> None:
163
+ """Fired when the user switches models via /model."""
164
+ track("model_selected", {"model": model, "provider": provider})
@@ -130,7 +130,31 @@ def load_config() -> dict:
130
130
  try:
131
131
  cfg.update(json.loads(CONFIG_FILE.read_text(encoding="utf-8")))
132
132
  except Exception:
133
- pass
133
+ # Config is corrupt (truncated write, crash mid-save, etc).
134
+ # NEVER silently reset the user's config: try the .bak backup
135
+ # first, and if that also fails, preserve the corrupt file for
136
+ # manual recovery instead of letting the next save_config()
137
+ # clobber it with bare defaults.
138
+ recovered = False
139
+ backup = CONFIG_FILE.with_suffix(".json.bak")
140
+ if backup.exists():
141
+ try:
142
+ cfg.update(json.loads(backup.read_text(encoding="utf-8")))
143
+ recovered = True
144
+ print(f"[dulus] WARNING: config.json was corrupt - recovered from {backup.name}")
145
+ except Exception:
146
+ pass
147
+ try:
148
+ import time as _time
149
+ quarantine = CONFIG_FILE.with_name(
150
+ f"config.corrupt-{int(_time.time())}.json")
151
+ CONFIG_FILE.replace(quarantine)
152
+ print(f"[dulus] Corrupt config preserved at: {quarantine}")
153
+ except Exception:
154
+ pass
155
+ if not recovered:
156
+ print("[dulus] WARNING: config.json was corrupt and no backup was found - "
157
+ "starting with defaults. Your old config was preserved (see above).")
134
158
  # Decrypt secured keys
135
159
  cfg = _unsecure_keys(cfg)
136
160
  # Backward-compat: legacy single api_key → anthropic_api_key
@@ -168,7 +192,23 @@ def save_config(cfg: dict):
168
192
  data = {k: v for k, v in cfg.items() if not k.startswith("_")}
169
193
  # Encrypt API keys before saving
170
194
  data = _secure_keys(dict(data))
171
- CONFIG_FILE.write_text(json.dumps(data, indent=2), encoding="utf-8")
195
+ payload = json.dumps(data, indent=2)
196
+ # Atomic write: dump to a temp file in the same dir, then replace.
197
+ # A crash mid-write can no longer truncate/corrupt config.json.
198
+ tmp = CONFIG_FILE.with_suffix(".json.tmp")
199
+ tmp.write_text(payload, encoding="utf-8")
200
+ # Keep a rolling backup of the last-known-good config so load_config()
201
+ # can auto-recover if the main file ever goes bad.
202
+ if CONFIG_FILE.exists():
203
+ try:
204
+ backup = CONFIG_FILE.with_suffix(".json.bak")
205
+ # Only back up if current file is valid JSON (don't overwrite a
206
+ # good backup with a corrupt file).
207
+ json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
208
+ backup.write_text(CONFIG_FILE.read_text(encoding="utf-8"), encoding="utf-8")
209
+ except Exception:
210
+ pass
211
+ tmp.replace(CONFIG_FILE)
172
212
 
173
213
 
174
214
  def current_provider(cfg: dict) -> str:
@@ -15,6 +15,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
15
  - Security policy
16
16
  - Brand guidelines
17
17
 
18
+ ## [3.6.2] - 2026-07-04
19
+
20
+ ### Fixed
21
+ - Opt-in telemetry never sent events: `MP_TOKEN` defaulted to empty string so
22
+ `is_enabled()` was always False, even after user consent. The public
23
+ project's write-only ingestion token now ships as the default
24
+ (`DULUS_MP_TOKEN` env var still overrides).
25
+
26
+ ### Added
27
+ - Named telemetry events (names/counts only — never content): `message_sent`,
28
+ `tool_used`, `command_used`, `model_selected`, wired into the REPL loop,
29
+ tool dispatch, slash commands and `/model`.
30
+ - Memory: session history search improvements — token matching, newest-first
31
+ ordering, no truncation (from 786bd34).
32
+
18
33
  ## [3.2.0] - 2026-05-30
19
34
 
20
35
  ### Added
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## 🔥🔥🔥 News (Pacific Time)
4
4
 
5
+ - July 3, 2026: **🌍 dulus.work — THE HUB, revealed.** The secret is out: dulus.work is a **global hub where every Dulus instance on the planet shares what it learns**. Your Dulus finds a fix, refines a skill, adapts a plugin locally → publishes it to the hub → every other Dulus in the world pulls it down. Not an update server — an **organism**: every installation is a node, every user effectively becomes a contributor. *The fix a Dulus in Tokyo finds at 3AM, the one in Santo Domingo has by sunrise.* Traditional agents scale linearly (N devs improving 1 product); Dulus scales with its network (every user is a dev). The building blocks have been quietly shipping for months — the SelfImprove engine, the Auto-Adapter, the skills format, cloudsave — and dulus.work is where they converge. Domain is live, execution underway. 🦅🇩🇴 → [dulus.work](https://dulus.work)
6
+
7
+
8
+ - July 3, 2026 (**v3.6.1**): **🛡️ Your config.json can never be silently reset again.** A crash mid-save could truncate `~/.dulus/config.json`; on the next start Dulus silently swallowed the parse error, dropped you into the first-run welcome wizard, and the first auto-save clobbered your real config with bare defaults (classic symptom: a config containing only `voice_lang`). Fixed with a triple safety net: **atomic writes** (temp file + rename — a mid-write crash physically can't corrupt the file anymore), a **rolling `config.json.bak`** of the last-known-good config, **auto-recovery** from that backup when the main file goes bad, and **quarantine** (`config.corrupt-<timestamp>.json`) so the broken original is always preserved for manual rescue instead of vanishing. If you ever lost your config to the welcome screen: this was it, and it's dead now.
5
9
 
6
10
  - July 3, 2026 (**v3.6.0**): **🧙 `/sage` (alias `/sabio`) — the AI studies your prompt before touching anything.** Inspired by ticket-intake classifier patterns but pointed the right way: instead of classifying requests into a database, Dulus decomposes YOUR request like a senior architect doing intake. Four phases: **INTAKE** (break the request into atomic sub-requests with category, priority and a verifiable "done"), **QUALITY GATE** (is it ambiguous? imprecise? mixing unrelated problems? — if missing info BLOCKS execution, ALL clarification questions arrive batched in one AskUserQuestion; otherwise assumptions are stated explicitly and it moves on), **PLAN** (ordered steps, tools per step, risks, verification — multi-step work registers via TaskCreate), **EXECUTE** (verify as you go, adapt honestly when reality diverges). `/sage` alone arms it one-shot for your NEXT prompt; `/sage <request>` runs the full treatment immediately; `/sage off|status` to manage. Difference vs `/plan`: plan mode is read-only planning for big projects — sage studies AND executes in one flow, built for the everyday "think before you act".
7
11
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dulus
3
- Version: 3.6.0
3
+ Version: 3.6.2
4
4
  Summary: Use IA as an agent without an API key. $0. Harvest your browser session for Gemini (guest, no login), Claude.ai, Claude Code, Kimi.com, Qwen, DeepSeek — drive them like an API, no per-token billing. Plus LiteLLM (100+ paid providers), local models via Ollama, /lang in 34 languages, Mesa Redonda, voice, OCR, MemPalace, embedded sandbox OS.
5
5
  Author: KevRojo
6
6
  License: GPL-3.0
@@ -133,7 +133,7 @@ SET /sticky_input ON since the first run for the best experience!
133
133
 
134
134
  > Full changelog: [`docs/news.md`](docs/news.md) · Inside the REPL: `/news`
135
135
 
136
- - **📡 dulus.work — announced.** Kevin finally revealed the final piece of the Dulus vision — the one he'd been keeping secret (even from Dulus itself 😂). Every agent ships updates. Nobody ships THIS. Already in motion. Stay tuned. 🦅 → [dulus.work](https://dulus.work)
136
+ - **🌍 dulus.work — THE HUB. The secret is out.** This is what Kevin had been hiding (even from Dulus itself 😂): **a global hub where every Dulus instance on the planet shares what it learns.** Your Dulus finds a fix, polishes a skill, adapts a plugin → it can publish it to the hub → every other Dulus in the world can pull it down. Not an update server an **organism**. Every installation is a node. *The fix a Dulus in Tokyo finds at 3AM, the one in Santo Domingo has by sunrise.* Other agents have N devs improving one product (linear). Dulus makes every user a dev (exponential). The pieces have been quietly shipping for months — SelfImprove engine, Auto-Adapter, the skills format, cloudsave — and now they converge. Domain is live, execution underway. 🦅🇩🇴 → [dulus.work](https://dulus.work)
137
137
 
138
138
  - **Dulus Agent — Telegram communities.** Dulus evolved from a CLI tool into a live AI agent inside Telegram groups. The first fully autonomous AI moderating and conversing in real communities — not a bot, not a filter, a real agent. Groups pay in $DULUS to activate him. We host Dulus for them — that's the business model. The CLI stays free forever. This is the paid layer. 🦅🇩🇴
139
139
 
@@ -1001,6 +1001,11 @@ def cmd_model(args: str, _state, config) -> bool:
1001
1001
  ok(f"Model set to {m} (provider: {pname})")
1002
1002
  from config import save_config
1003
1003
  save_config(config)
1004
+ try:
1005
+ import analytics as _telemetry
1006
+ _telemetry.track_model_selected(m, pname)
1007
+ except Exception:
1008
+ pass
1004
1009
  return True
1005
1010
 
1006
1011
  def _generate_personas(topic: str, curr_model: str, config: dict, count: int = 5) -> dict | None:
@@ -9753,6 +9758,11 @@ def handle_slash(line: str, state, config) -> Union[bool, tuple]:
9753
9758
  args = parts[1] if len(parts) > 1 else ""
9754
9759
  handler = COMMANDS.get(cmd)
9755
9760
  if handler:
9761
+ try:
9762
+ import analytics as _telemetry
9763
+ _telemetry.track_command_used(cmd)
9764
+ except Exception:
9765
+ pass
9756
9766
  result = handler(args, state, config)
9757
9767
  # cmd_voice/cmd_image/cmd_brainstorm/cmd_plan return sentinels to ask the REPL to run_query
9758
9768
  if isinstance(result, tuple) and result[0] in ("__voice__", "__image__", "__video__", "__brainstorm__", "__worker__", "__ssj_cmd__", "__ssj_query__", "__ssj_debate__", "__ssj_passthrough__", "__ssj_promote_worker__", "__plan__", "__sage__", "__plugin_main_agent__", "__roundtable__", "__roundtable_stop__"):
@@ -10624,6 +10634,12 @@ def repl(config: dict, initial_prompt: str = None):
10624
10634
  _duplicate_suppressed = False
10625
10635
 
10626
10636
  try:
10637
+ if not is_background:
10638
+ try:
10639
+ import analytics as _telemetry
10640
+ _telemetry.track_message_sent(str(config.get("model", "")))
10641
+ except Exception:
10642
+ pass
10627
10643
  for event in run(user_input, state, config, system_prompt):
10628
10644
  # Stop spinner only when visible output arrives
10629
10645
  if spinner_shown:
@@ -10688,6 +10704,11 @@ def repl(config: dict, initial_prompt: str = None):
10688
10704
  if event.name == "AskUserQuestion":
10689
10705
  _stop_tool_spinner()
10690
10706
  print_tool_start(event.name, event.inputs, verbose)
10707
+ try:
10708
+ import analytics as _telemetry
10709
+ _telemetry.track_tool_used(event.name)
10710
+ except Exception:
10711
+ pass
10691
10712
 
10692
10713
  elif isinstance(event, PermissionRequest):
10693
10714
  _stop_tool_spinner()
@@ -62,39 +62,71 @@ def search_session_history(query: str, max_results: int = 5) -> list[dict]:
62
62
  except Exception:
63
63
  continue
64
64
 
65
- # 3. Perform search
65
+ # 3. Perform search — NEWEST sessions first, token-based matching
66
+ import re
67
+ q_tokens = [t for t in re.split(r"\W+", query) if len(t) > 2]
68
+ if not q_tokens:
69
+ q_tokens = [query] if query else []
70
+ if not q_tokens:
71
+ return []
72
+ # Require a majority of tokens (at least half, min 1) so multi-word
73
+ # queries still hit even if one word is missing from the message.
74
+ min_hits = max(1, (len(q_tokens) + 1) // 2)
75
+
76
+ # Search most recent sessions first (this is where answers usually live)
77
+ all_sessions.sort(key=lambda s: str(s.get("saved_at", "")), reverse=True)
78
+
66
79
  results = []
67
80
  for sess in all_sessions:
68
81
  session_id = sess.get("session_id", "unknown")
69
82
  saved_at = sess.get("saved_at", "unknown")
70
83
  messages = sess.get("messages", [])
71
-
84
+
72
85
  session_hits = []
86
+ best_score = 0
73
87
  for msg in messages:
74
88
  content = msg.get("content", "")
75
89
  if not isinstance(content, str):
76
90
  continue
77
-
78
- if query in content.lower():
79
- # Extract snippet
80
- start = max(0, content.lower().find(query) - 60)
91
+
92
+ low = content.lower()
93
+ matched = [t for t in q_tokens if t in low]
94
+ # Accept: full literal phrase OR majority of query tokens
95
+ if query in low or len(matched) >= min_hits:
96
+ anchor = query if query in low else matched[0]
97
+ # Extract snippet around the first matched token
98
+ start = max(0, low.find(anchor) - 60)
81
99
  end = min(len(content), start + 200)
82
100
  snippet = content[start:end].replace("\n", " ")
83
101
  if start > 0: snippet = "..." + snippet
84
102
  if end < len(content): snippet += "..."
85
-
103
+
104
+ score = len(q_tokens) + 1 if query in low else len(matched)
105
+ best_score = max(best_score, score)
86
106
  session_hits.append({
87
107
  "role": msg.get("role"),
88
- "snippet": snippet
108
+ "snippet": snippet,
109
+ "_score": score,
89
110
  })
90
-
111
+
91
112
  if session_hits:
113
+ # Best-matching hits first within the session
114
+ session_hits.sort(key=lambda h: h["_score"], reverse=True)
115
+ for h in session_hits:
116
+ h.pop("_score", None)
92
117
  results.append({
93
118
  "session_id": session_id,
94
119
  "saved_at": saved_at,
95
- "hits": session_hits[:3] # limit hits per session to avoid bloat
120
+ "hits": session_hits[:3], # limit hits per session to avoid bloat
121
+ "_score": best_score,
96
122
  })
123
+ # Early exit: sessions are newest-first, once we have enough
124
+ # strong matches there is no need to scan ancient history.
125
+ if len(results) >= max_results * 3:
126
+ break
97
127
 
98
- # Sort sessions by recency (newest hit first)
99
- results.sort(key=lambda x: x["saved_at"], reverse=True)
128
+ # Rank: match quality first, then recency (both already newest-first)
129
+ results.sort(key=lambda x: (x["_score"], str(x["saved_at"])), reverse=True)
130
+ for r in results:
131
+ r.pop("_score", None)
100
132
  return results[:max_results]
@@ -123,17 +123,24 @@ def _memory_search(params: dict, config: dict) -> str:
123
123
  query = params["query"]
124
124
  use_ai = params.get("use_ai", False)
125
125
 
126
- if config.get("ULTRA_SEARCH") in (1, "1", True, "true"):
126
+ ultra = config.get("ULTRA_SEARCH") in (1, "1", True, "true")
127
+ if ultra:
127
128
  params["include_sessions"] = True
128
- max_results = max(params.get("max_results", 5), 100)
129
+ # Sessions get the big budget; memories stay capped so session
130
+ # matches don't get buried/truncated under 100 memory entries.
131
+ max_results = max(params.get("max_results", 5), 10)
132
+ session_max = 20
129
133
  else:
130
134
  max_results = params.get("max_results", 5)
135
+ session_max = max_results
131
136
 
132
137
  results = find_relevant_memories(
133
138
  query, max_results=max_results * 3, use_ai=use_ai, config=config
134
139
  )
135
140
 
136
- if not results:
141
+ if not results and not params.get("include_sessions"):
142
+ # Only early-return when sessions won't be searched; otherwise a
143
+ # zero-memory result would skip session history entirely (old bug).
137
144
  return f"No memories found matching '{query}'."
138
145
 
139
146
  # Re-rank by confidence × recency score
@@ -172,15 +179,22 @@ def _memory_search(params: dict, config: dict) -> str:
172
179
  should_search_sessions = params.get("include_sessions")
173
180
 
174
181
  if should_search_sessions:
175
- sess_results = search_session_history(query, max_results=max_results)
182
+ sess_results = search_session_history(query, max_results=session_max)
176
183
  if sess_results:
177
- lines.append("\n" + "─" * 40)
178
- lines.append(f"Historical Session Matches ({len(sess_results)} sessions):")
184
+ sess_lines = ["─" * 40,
185
+ f"Historical Session Matches ({len(sess_results)} sessions, newest first):"]
179
186
  for sr in sess_results:
180
- lines.append(f"\nSession {sr['session_id']} ({sr['saved_at']})")
187
+ sess_lines.append(f"\nSession {sr['session_id']} ({sr['saved_at']})")
181
188
  for h in sr["hits"]:
182
189
  role_lbl = "User" if h["role"] == "user" else "Dulus"
183
- lines.append(f" [{role_lbl}] {h['snippet']}")
190
+ sess_lines.append(f" [{role_lbl}] {h['snippet']}")
191
+ if ultra:
192
+ # ULTRA mode: sessions are the primary target — put them FIRST
193
+ # so they never get truncated away under memory entries.
194
+ lines = [lines[0], ""] + sess_lines + ["", "─" * 40, "Memory entries:"] + lines[1:]
195
+ else:
196
+ lines.append("")
197
+ lines.extend(sess_lines)
184
198
 
185
199
  # ── Part 3: Offloaded Jobs Search ────────────────────────────────────
186
200
  try:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dulus"
7
- version = "3.6.0"
7
+ version = "3.6.2"
8
8
  description = "Use IA as an agent without an API key. $0. Harvest your browser session for Gemini (guest, no login), Claude.ai, Claude Code, Kimi.com, Qwen, DeepSeek — drive them like an API, no per-token billing. Plus LiteLLM (100+ paid providers), local models via Ollama, /lang in 34 languages, Mesa Redonda, voice, OCR, MemPalace, embedded sandbox OS."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
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