dulus 3.9.5__tar.gz → 3.10.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 (347) hide show
  1. {dulus-3.9.5 → dulus-3.10.2}/MANIFEST.in +1 -0
  2. {dulus-3.9.5/dulus.egg-info → dulus-3.10.2}/PKG-INFO +1 -1
  3. {dulus-3.9.5 → dulus-3.10.2}/agent.py +4 -0
  4. {dulus-3.9.5 → dulus-3.10.2}/analytics.py +3 -71
  5. dulus-3.10.2/display_blocks.py +529 -0
  6. {dulus-3.9.5 → dulus-3.10.2}/docs/CHANGELOG.md +46 -0
  7. {dulus-3.9.5 → dulus-3.10.2}/docs/dashboard/index.html +0 -10
  8. {dulus-3.9.5 → dulus-3.10.2}/docs/index.html +0 -10
  9. {dulus-3.9.5 → dulus-3.10.2}/docs/news.md +6 -0
  10. {dulus-3.9.5 → dulus-3.10.2}/docs/personas/index.html +0 -10
  11. {dulus-3.9.5 → dulus-3.10.2/dulus.egg-info}/PKG-INFO +1 -1
  12. {dulus-3.9.5 → dulus-3.10.2}/dulus.egg-info/SOURCES.txt +13 -1
  13. {dulus-3.9.5 → dulus-3.10.2}/dulus.egg-info/top_level.txt +9 -1
  14. {dulus-3.9.5 → dulus-3.10.2}/dulus.py +53 -8
  15. {dulus-3.9.5 → dulus-3.10.2}/dulus_mcp/commands.py +182 -182
  16. {dulus-3.9.5 → dulus-3.10.2}/dulus_mcp/hub.py +845 -845
  17. dulus-3.10.2/file_filter.py +91 -0
  18. dulus-3.10.2/paste_placeholders.py +49 -0
  19. dulus-3.10.2/profiles.py +340 -0
  20. {dulus-3.9.5 → dulus-3.10.2}/pyproject.toml +10 -1
  21. dulus-3.10.2/steer_input.py +148 -0
  22. dulus-3.10.2/tools_background.py +416 -0
  23. dulus-3.10.2/tools_think.py +68 -0
  24. dulus-3.10.2/tools_todo.py +196 -0
  25. {dulus-3.9.5 → dulus-3.10.2}/updater.py +178 -178
  26. {dulus-3.9.5 → dulus-3.10.2}/webchat_server.py +98 -68
  27. dulus-3.10.2/webchat_ui/__init__.py +1 -0
  28. dulus-3.10.2/webchat_ui/app.js +1585 -0
  29. dulus-3.10.2/webchat_ui/index.html +349 -0
  30. dulus-3.10.2/webchat_ui/style.css +1802 -0
  31. {dulus-3.9.5 → dulus-3.10.2}/LICENSE +0 -0
  32. {dulus-3.9.5 → dulus-3.10.2}/README.md +0 -0
  33. {dulus-3.9.5 → dulus-3.10.2}/backend/__init__.py +0 -0
  34. {dulus-3.9.5 → dulus-3.10.2}/backend/agents_bridge.py +0 -0
  35. {dulus-3.9.5 → dulus-3.10.2}/backend/compressor.py +0 -0
  36. {dulus-3.9.5 → dulus-3.10.2}/backend/context.py +0 -0
  37. {dulus-3.9.5 → dulus-3.10.2}/backend/githook.py +0 -0
  38. {dulus-3.9.5 → dulus-3.10.2}/backend/marketplace.py +0 -0
  39. {dulus-3.9.5 → dulus-3.10.2}/backend/mempalace_bridge.py +0 -0
  40. {dulus-3.9.5 → dulus-3.10.2}/backend/personas.py +0 -0
  41. {dulus-3.9.5 → dulus-3.10.2}/backend/plugins.py +0 -0
  42. {dulus-3.9.5 → dulus-3.10.2}/backend/server.py +0 -0
  43. {dulus-3.9.5 → dulus-3.10.2}/backend/tasks.py +0 -0
  44. {dulus-3.9.5 → dulus-3.10.2}/batch_api.py +0 -0
  45. {dulus-3.9.5 → dulus-3.10.2}/checkpoint/__init__.py +0 -0
  46. {dulus-3.9.5 → dulus-3.10.2}/checkpoint/hooks.py +0 -0
  47. {dulus-3.9.5 → dulus-3.10.2}/checkpoint/store.py +0 -0
  48. {dulus-3.9.5 → dulus-3.10.2}/checkpoint/types.py +0 -0
  49. {dulus-3.9.5 → dulus-3.10.2}/claude_code_watcher.py +0 -0
  50. {dulus-3.9.5 → dulus-3.10.2}/clipboard_utils.py +0 -0
  51. {dulus-3.9.5 → dulus-3.10.2}/cloudsave.py +0 -0
  52. {dulus-3.9.5 → dulus-3.10.2}/common.py +0 -0
  53. {dulus-3.9.5 → dulus-3.10.2}/compaction.py +0 -0
  54. {dulus-3.9.5 → dulus-3.10.2}/config.py +0 -0
  55. {dulus-3.9.5 → dulus-3.10.2}/context.py +0 -0
  56. {dulus-3.9.5 → dulus-3.10.2}/data/__init__.py +0 -0
  57. {dulus-3.9.5 → dulus-3.10.2}/data/active_persona.json +0 -0
  58. {dulus-3.9.5 → dulus-3.10.2}/data/marketplace.json +0 -0
  59. {dulus-3.9.5 → dulus-3.10.2}/data/personas.json +0 -0
  60. {dulus-3.9.5 → dulus-3.10.2}/data/plugins/__init__.py +0 -0
  61. {dulus-3.9.5 → dulus-3.10.2}/data/plugins/composio/__init__.py +0 -0
  62. {dulus-3.9.5 → dulus-3.10.2}/data/plugins/composio/composio_plugin/__init__.py +0 -0
  63. {dulus-3.9.5 → dulus-3.10.2}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
  64. {dulus-3.9.5 → dulus-3.10.2}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
  65. {dulus-3.9.5 → dulus-3.10.2}/data/plugins/composio/plugin.json +0 -0
  66. {dulus-3.9.5 → dulus-3.10.2}/data/plugins/composio/plugin_tool.py +0 -0
  67. {dulus-3.9.5 → dulus-3.10.2}/data/tasks.json +0 -0
  68. {dulus-3.9.5 → dulus-3.10.2}/docs/API.md +0 -0
  69. {dulus-3.9.5 → dulus-3.10.2}/docs/BRAND_GUIDE.md +0 -0
  70. {dulus-3.9.5 → dulus-3.10.2}/docs/CONTRIBUTING.md +0 -0
  71. {dulus-3.9.5 → dulus-3.10.2}/docs/DEPLOYMENT.md +0 -0
  72. {dulus-3.9.5 → dulus-3.10.2}/docs/Dulus_AI_Whitepaper_(v2.0).pdf +0 -0
  73. {dulus-3.9.5 → dulus-3.10.2}/docs/FAQ.md +0 -0
  74. {dulus-3.9.5 → dulus-3.10.2}/docs/GETTING_STARTED.md +0 -0
  75. {dulus-3.9.5 → dulus-3.10.2}/docs/INVESTOR_BRIEF.md +0 -0
  76. {dulus-3.9.5 → dulus-3.10.2}/docs/README.md +0 -0
  77. {dulus-3.9.5 → dulus-3.10.2}/docs/README_AR.md +0 -0
  78. {dulus-3.9.5 → dulus-3.10.2}/docs/README_EN.md +0 -0
  79. {dulus-3.9.5 → dulus-3.10.2}/docs/README_ES.md +0 -0
  80. {dulus-3.9.5 → dulus-3.10.2}/docs/README_FR.md +0 -0
  81. {dulus-3.9.5 → dulus-3.10.2}/docs/README_JA.md +0 -0
  82. {dulus-3.9.5 → dulus-3.10.2}/docs/README_KO.md +0 -0
  83. {dulus-3.9.5 → dulus-3.10.2}/docs/README_PT.md +0 -0
  84. {dulus-3.9.5 → dulus-3.10.2}/docs/README_RU.md +0 -0
  85. {dulus-3.9.5 → dulus-3.10.2}/docs/README_ZH.md +0 -0
  86. {dulus-3.9.5 → dulus-3.10.2}/docs/SECURITY.md +0 -0
  87. {dulus-3.9.5 → dulus-3.10.2}/docs/__init__.py +0 -0
  88. {dulus-3.9.5 → dulus-3.10.2}/docs/api.html +0 -0
  89. {dulus-3.9.5 → dulus-3.10.2}/docs/architecture.md +0 -0
  90. {dulus-3.9.5 → dulus-3.10.2}/docs/azure-speech-template.json +0 -0
  91. {dulus-3.9.5 → dulus-3.10.2}/docs/divider.svg +0 -0
  92. {dulus-3.9.5 → dulus-3.10.2}/docs/dulus-bird.png +0 -0
  93. {dulus-3.9.5 → dulus-3.10.2}/docs/generate.py +0 -0
  94. {dulus-3.9.5 → dulus-3.10.2}/docs/hero.svg +0 -0
  95. {dulus-3.9.5 → dulus-3.10.2}/docs/nvidia-models.svg +0 -0
  96. {dulus-3.9.5 → dulus-3.10.2}/docs/particle-playground.html +0 -0
  97. {dulus-3.9.5 → dulus-3.10.2}/docs/poetry-banner.png +0 -0
  98. {dulus-3.9.5 → dulus-3.10.2}/docs/preview.html +0 -0
  99. {dulus-3.9.5 → dulus-3.10.2}/docs/sec-agents.svg +0 -0
  100. {dulus-3.9.5 → dulus-3.10.2}/docs/sec-brainstorm.svg +0 -0
  101. {dulus-3.9.5 → dulus-3.10.2}/docs/sec-bridges.svg +0 -0
  102. {dulus-3.9.5 → dulus-3.10.2}/docs/sec-features.svg +0 -0
  103. {dulus-3.9.5 → dulus-3.10.2}/docs/sec-freetier.svg +0 -0
  104. {dulus-3.9.5 → dulus-3.10.2}/docs/sec-memory.svg +0 -0
  105. {dulus-3.9.5 → dulus-3.10.2}/docs/sec-models.svg +0 -0
  106. {dulus-3.9.5 → dulus-3.10.2}/docs/sec-perms.svg +0 -0
  107. {dulus-3.9.5 → dulus-3.10.2}/docs/sec-plugins.svg +0 -0
  108. {dulus-3.9.5 → dulus-3.10.2}/docs/sec-quickstart.svg +0 -0
  109. {dulus-3.9.5 → dulus-3.10.2}/docs/sec-ssj.svg +0 -0
  110. {dulus-3.9.5 → dulus-3.10.2}/docs/spinners.svg +0 -0
  111. {dulus-3.9.5 → dulus-3.10.2}/docs/split-pane.svg +0 -0
  112. {dulus-3.9.5 → dulus-3.10.2}/docs/terminal-boot.svg +0 -0
  113. {dulus-3.9.5 → dulus-3.10.2}/dulus.egg-info/dependency_links.txt +0 -0
  114. {dulus-3.9.5 → dulus-3.10.2}/dulus.egg-info/entry_points.txt +0 -0
  115. {dulus-3.9.5 → dulus-3.10.2}/dulus.egg-info/requires.txt +0 -0
  116. {dulus-3.9.5 → dulus-3.10.2}/dulus_gui.py +0 -0
  117. {dulus-3.9.5 → dulus-3.10.2}/dulus_gui_web.py +0 -0
  118. {dulus-3.9.5 → dulus-3.10.2}/dulus_mcp/__init__.py +0 -0
  119. {dulus-3.9.5 → dulus-3.10.2}/dulus_mcp/client.py +0 -0
  120. {dulus-3.9.5 → dulus-3.10.2}/dulus_mcp/config.py +0 -0
  121. {dulus-3.9.5 → dulus-3.10.2}/dulus_mcp/tools.py +0 -0
  122. {dulus-3.9.5 → dulus-3.10.2}/dulus_mcp/types.py +0 -0
  123. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/__init__.py +0 -0
  124. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/add_dir_manager.py +0 -0
  125. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/afk_mode.py +0 -0
  126. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/approval_runtime.py +0 -0
  127. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/background_tasks.py +0 -0
  128. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/clipboard_utils.py +0 -0
  129. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/diff_visualizer.py +0 -0
  130. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/export_import.py +0 -0
  131. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/hook_engine.py +0 -0
  132. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/notification_manager.py +0 -0
  133. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/session_fork.py +0 -0
  134. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/shell_mode.py +0 -0
  135. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/todo_visualizer.py +0 -0
  136. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/tree_ls.py +0 -0
  137. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/wire_events.py +0 -0
  138. {dulus-3.9.5 → dulus-3.10.2}/dulus_tools/yolo_mode.py +0 -0
  139. {dulus-3.9.5 → dulus-3.10.2}/governance.py +0 -0
  140. {dulus-3.9.5 → dulus-3.10.2}/gui/__init__.py +0 -0
  141. {dulus-3.9.5 → dulus-3.10.2}/gui/agent_bridge.py +0 -0
  142. {dulus-3.9.5 → dulus-3.10.2}/gui/chat_widget.py +0 -0
  143. {dulus-3.9.5 → dulus-3.10.2}/gui/dulus_orchestrator/App.tsx +0 -0
  144. {dulus-3.9.5 → dulus-3.10.2}/gui/dulus_orchestrator/THIRD_PARTY_NOTICES.md +0 -0
  145. {dulus-3.9.5 → dulus-3.10.2}/gui/dulus_orchestrator/dist/index.html +0 -0
  146. {dulus-3.9.5 → dulus-3.10.2}/gui/dulus_orchestrator/dist/standalone.html +0 -0
  147. {dulus-3.9.5 → dulus-3.10.2}/gui/dulus_orchestrator/index.html +0 -0
  148. {dulus-3.9.5 → dulus-3.10.2}/gui/dulus_orchestrator/index.tsx +0 -0
  149. {dulus-3.9.5 → dulus-3.10.2}/gui/dulus_orchestrator/metadata.json +0 -0
  150. {dulus-3.9.5 → dulus-3.10.2}/gui/dulus_orchestrator/package-lock.json +0 -0
  151. {dulus-3.9.5 → dulus-3.10.2}/gui/dulus_orchestrator/package.json +0 -0
  152. {dulus-3.9.5 → dulus-3.10.2}/gui/dulus_orchestrator/types.ts +0 -0
  153. {dulus-3.9.5 → dulus-3.10.2}/gui/dulus_orchestrator/utils.ts +0 -0
  154. {dulus-3.9.5 → dulus-3.10.2}/gui/dulus_orchestrator/vertex-ai-proxy-interceptor.js +0 -0
  155. {dulus-3.9.5 → dulus-3.10.2}/gui/dulus_orchestrator/vite.config.ts +0 -0
  156. {dulus-3.9.5 → dulus-3.10.2}/gui/main_window.py +0 -0
  157. {dulus-3.9.5 → dulus-3.10.2}/gui/personas.py +0 -0
  158. {dulus-3.9.5 → dulus-3.10.2}/gui/sandbox_server.py +0 -0
  159. {dulus-3.9.5 → dulus-3.10.2}/gui/session_utils.py +0 -0
  160. {dulus-3.9.5 → dulus-3.10.2}/gui/settings_dialog.py +0 -0
  161. {dulus-3.9.5 → dulus-3.10.2}/gui/sidebar.py +0 -0
  162. {dulus-3.9.5 → dulus-3.10.2}/gui/tasks_view.py +0 -0
  163. {dulus-3.9.5 → dulus-3.10.2}/gui/themes.py +0 -0
  164. {dulus-3.9.5 → dulus-3.10.2}/gui/tool_panel.py +0 -0
  165. {dulus-3.9.5 → dulus-3.10.2}/input.py +0 -0
  166. {dulus-3.9.5 → dulus-3.10.2}/license_manager.py +0 -0
  167. {dulus-3.9.5 → dulus-3.10.2}/memory/__init__.py +0 -0
  168. {dulus-3.9.5 → dulus-3.10.2}/memory/audit.py +0 -0
  169. {dulus-3.9.5 → dulus-3.10.2}/memory/consolidator.py +0 -0
  170. {dulus-3.9.5 → dulus-3.10.2}/memory/context.py +0 -0
  171. {dulus-3.9.5 → dulus-3.10.2}/memory/offload.py +0 -0
  172. {dulus-3.9.5 → dulus-3.10.2}/memory/palace.py +0 -0
  173. {dulus-3.9.5 → dulus-3.10.2}/memory/scan.py +0 -0
  174. {dulus-3.9.5 → dulus-3.10.2}/memory/sessions.py +0 -0
  175. {dulus-3.9.5 → dulus-3.10.2}/memory/store.py +0 -0
  176. {dulus-3.9.5 → dulus-3.10.2}/memory/tools.py +0 -0
  177. {dulus-3.9.5 → dulus-3.10.2}/memory/types.py +0 -0
  178. {dulus-3.9.5 → dulus-3.10.2}/memory/vector_search.py +0 -0
  179. {dulus-3.9.5 → dulus-3.10.2}/multi_agent/__init__.py +0 -0
  180. {dulus-3.9.5 → dulus-3.10.2}/multi_agent/subagent.py +0 -0
  181. {dulus-3.9.5 → dulus-3.10.2}/multi_agent/tools.py +0 -0
  182. {dulus-3.9.5 → dulus-3.10.2}/offload_helper.py +0 -0
  183. {dulus-3.9.5 → dulus-3.10.2}/personality_config.py +0 -0
  184. {dulus-3.9.5 → dulus-3.10.2}/plugin/__init__.py +0 -0
  185. {dulus-3.9.5 → dulus-3.10.2}/plugin/autoadapter.py +0 -0
  186. {dulus-3.9.5 → dulus-3.10.2}/plugin/loader.py +0 -0
  187. {dulus-3.9.5 → dulus-3.10.2}/plugin/recommend.py +0 -0
  188. {dulus-3.9.5 → dulus-3.10.2}/plugin/store.py +0 -0
  189. {dulus-3.9.5 → dulus-3.10.2}/plugin/types.py +0 -0
  190. {dulus-3.9.5 → dulus-3.10.2}/providers.py +0 -0
  191. {dulus-3.9.5 → dulus-3.10.2}/sandbox/README.md +0 -0
  192. {dulus-3.9.5 → dulus-3.10.2}/sandbox/__init__.py +0 -0
  193. {dulus-3.9.5 → dulus-3.10.2}/sandbox/components.json +0 -0
  194. {dulus-3.9.5 → dulus-3.10.2}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
  195. {dulus-3.9.5 → dulus-3.10.2}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
  196. {dulus-3.9.5 → dulus-3.10.2}/sandbox/dist/index.html +0 -0
  197. {dulus-3.9.5 → dulus-3.10.2}/sandbox/dist/wallpaper-default.jpg +0 -0
  198. {dulus-3.9.5 → dulus-3.10.2}/sandbox/dist/wallpapers/default.jpeg +0 -0
  199. {dulus-3.9.5 → dulus-3.10.2}/sandbox/dist/wallpapers/light.jpeg +0 -0
  200. {dulus-3.9.5 → dulus-3.10.2}/sandbox/dist/wallpapers/nature.jpeg +0 -0
  201. {dulus-3.9.5 → dulus-3.10.2}/sandbox/dist/wallpapers/tech.jpeg +0 -0
  202. {dulus-3.9.5 → dulus-3.10.2}/sandbox/eslint.config.js +0 -0
  203. {dulus-3.9.5 → dulus-3.10.2}/sandbox/index.html +0 -0
  204. {dulus-3.9.5 → dulus-3.10.2}/sandbox/info.md +0 -0
  205. {dulus-3.9.5 → dulus-3.10.2}/sandbox/package-lock.json +0 -0
  206. {dulus-3.9.5 → dulus-3.10.2}/sandbox/package.json +0 -0
  207. {dulus-3.9.5 → dulus-3.10.2}/sandbox/postcss.config.js +0 -0
  208. {dulus-3.9.5 → dulus-3.10.2}/sandbox/tailwind.config.js +0 -0
  209. {dulus-3.9.5 → dulus-3.10.2}/sandbox/tsconfig.app.json +0 -0
  210. {dulus-3.9.5 → dulus-3.10.2}/sandbox/tsconfig.json +0 -0
  211. {dulus-3.9.5 → dulus-3.10.2}/sandbox/tsconfig.node.json +0 -0
  212. {dulus-3.9.5 → dulus-3.10.2}/sandbox/vite.config.ts +0 -0
  213. {dulus-3.9.5 → dulus-3.10.2}/sandbox_bootstrap.py +0 -0
  214. {dulus-3.9.5 → dulus-3.10.2}/setup.cfg +0 -0
  215. {dulus-3.9.5 → dulus-3.10.2}/skill/__init__.py +0 -0
  216. {dulus-3.9.5 → dulus-3.10.2}/skill/builtin.py +0 -0
  217. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/LICENSE-obsidian-skills +0 -0
  218. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/README.md +0 -0
  219. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/defuddle/SKILL.md +0 -0
  220. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/json-canvas/SKILL.md +0 -0
  221. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/json-canvas/references/EXAMPLES.md +0 -0
  222. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/obsidian-bases/SKILL.md +0 -0
  223. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/obsidian-bases/references/FUNCTIONS_REFERENCE.md +0 -0
  224. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/obsidian-cli/SKILL.md +0 -0
  225. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/obsidian-markdown/SKILL.md +0 -0
  226. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/obsidian-markdown/references/CALLOUTS.md +0 -0
  227. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/obsidian-markdown/references/EMBEDS.md +0 -0
  228. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/obsidian-markdown/references/PROPERTIES.md +0 -0
  229. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/README.md +0 -0
  230. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/SKILL.md +0 -0
  231. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/addresses.json +0 -0
  232. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/advancedbackgroundchecks.json +0 -0
  233. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/beenverified.json +0 -0
  234. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/clustal.json +0 -0
  235. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/clustrmaps.json +0 -0
  236. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/cyberbackgroundchecks.json +0 -0
  237. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/familytreenow.json +0 -0
  238. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/fastpeoplesearch.json +0 -0
  239. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/intelius.json +0 -0
  240. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/mylife.json +0 -0
  241. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/nuwber.json +0 -0
  242. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/peekyou.json +0 -0
  243. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/peoplefinders.json +0 -0
  244. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/radaris.json +0 -0
  245. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/rehold.json +0 -0
  246. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/searchpeoplefree.json +0 -0
  247. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/socialcatfish.json +0 -0
  248. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/spokeo.json +0 -0
  249. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/thatsthem.json +0 -0
  250. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/truepeoplesearch.json +0 -0
  251. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/usphonebook.json +0 -0
  252. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/brokers/whitepages.json +0 -0
  253. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/legal/ccpa.md +0 -0
  254. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/legal/drop.md +0 -0
  255. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/legal/gdpr.md +0 -0
  256. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/methods.md +0 -0
  257. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/site-playbooks.md +0 -0
  258. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/references/state-machine.md +0 -0
  259. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/autopilot.py +0 -0
  260. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/badbool.py +0 -0
  261. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/brokers.py +0 -0
  262. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/cdp.py +0 -0
  263. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/config.py +0 -0
  264. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/crypto.py +0 -0
  265. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/dossier.py +0 -0
  266. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/email_modes.py +0 -0
  267. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/emailer.py +0 -0
  268. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/ledger.py +0 -0
  269. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/legal.py +0 -0
  270. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/paths.py +0 -0
  271. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/pdd.py +0 -0
  272. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/registry.py +0 -0
  273. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/report.py +0 -0
  274. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/scan.py +0 -0
  275. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/storage.py +0 -0
  276. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/tiers.py +0 -0
  277. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/scripts/vectors.py +0 -0
  278. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/templates/consent/authorization.md +0 -0
  279. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/templates/emails/ccpa-authorized-agent.txt +0 -0
  280. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/templates/emails/ccpa-deletion.txt +0 -0
  281. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/templates/emails/ccpa-indirect-deletion.txt +0 -0
  282. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/templates/emails/gdpr-erasure.txt +0 -0
  283. {dulus-3.9.5 → dulus-3.10.2}/skill/bundled/unbroker/templates/emails/generic-optout.txt +0 -0
  284. {dulus-3.9.5 → dulus-3.10.2}/skill/clawhub.py +0 -0
  285. {dulus-3.9.5 → dulus-3.10.2}/skill/executor.py +0 -0
  286. {dulus-3.9.5 → dulus-3.10.2}/skill/loader.py +0 -0
  287. {dulus-3.9.5 → dulus-3.10.2}/skill/tools.py +0 -0
  288. {dulus-3.9.5 → dulus-3.10.2}/skills.py +0 -0
  289. {dulus-3.9.5 → dulus-3.10.2}/soul.py +0 -0
  290. {dulus-3.9.5 → dulus-3.10.2}/spinner.py +0 -0
  291. {dulus-3.9.5 → dulus-3.10.2}/string_utils.py +0 -0
  292. {dulus-3.9.5 → dulus-3.10.2}/subagent.py +0 -0
  293. {dulus-3.9.5 → dulus-3.10.2}/task/__init__.py +0 -0
  294. {dulus-3.9.5 → dulus-3.10.2}/task/store.py +0 -0
  295. {dulus-3.9.5 → dulus-3.10.2}/task/tools.py +0 -0
  296. {dulus-3.9.5 → dulus-3.10.2}/task/types.py +0 -0
  297. {dulus-3.9.5 → dulus-3.10.2}/tests/test_afk_yolo.py +0 -0
  298. {dulus-3.9.5 → dulus-3.10.2}/tests/test_approval_runtime.py +0 -0
  299. {dulus-3.9.5 → dulus-3.10.2}/tests/test_background_task_tools.py +0 -0
  300. {dulus-3.9.5 → dulus-3.10.2}/tests/test_background_tasks.py +0 -0
  301. {dulus-3.9.5 → dulus-3.10.2}/tests/test_checkpoint.py +0 -0
  302. {dulus-3.9.5 → dulus-3.10.2}/tests/test_clipboard_utils.py +0 -0
  303. {dulus-3.9.5 → dulus-3.10.2}/tests/test_compaction.py +0 -0
  304. {dulus-3.9.5 → dulus-3.10.2}/tests/test_diff_view.py +0 -0
  305. {dulus-3.9.5 → dulus-3.10.2}/tests/test_diff_visualization.py +0 -0
  306. {dulus-3.9.5 → dulus-3.10.2}/tests/test_display_blocks.py +0 -0
  307. {dulus-3.9.5 → dulus-3.10.2}/tests/test_export_import.py +0 -0
  308. {dulus-3.9.5 → dulus-3.10.2}/tests/test_hook_engine.py +0 -0
  309. {dulus-3.9.5 → dulus-3.10.2}/tests/test_injection_fix.py +0 -0
  310. {dulus-3.9.5 → dulus-3.10.2}/tests/test_license.py +0 -0
  311. {dulus-3.9.5 → dulus-3.10.2}/tests/test_mcp.py +0 -0
  312. {dulus-3.9.5 → dulus-3.10.2}/tests/test_memory.py +0 -0
  313. {dulus-3.9.5 → dulus-3.10.2}/tests/test_notification_manager.py +0 -0
  314. {dulus-3.9.5 → dulus-3.10.2}/tests/test_plugin.py +0 -0
  315. {dulus-3.9.5 → dulus-3.10.2}/tests/test_session_fork.py +0 -0
  316. {dulus-3.9.5 → dulus-3.10.2}/tests/test_shell_mode.py +0 -0
  317. {dulus-3.9.5 → dulus-3.10.2}/tests/test_skills.py +0 -0
  318. {dulus-3.9.5 → dulus-3.10.2}/tests/test_steer_input.py +0 -0
  319. {dulus-3.9.5 → dulus-3.10.2}/tests/test_subagent.py +0 -0
  320. {dulus-3.9.5 → dulus-3.10.2}/tests/test_task.py +0 -0
  321. {dulus-3.9.5 → dulus-3.10.2}/tests/test_telegram_buffer.py +0 -0
  322. {dulus-3.9.5 → dulus-3.10.2}/tests/test_think_tool.py +0 -0
  323. {dulus-3.9.5 → dulus-3.10.2}/tests/test_todo_tool.py +0 -0
  324. {dulus-3.9.5 → dulus-3.10.2}/tests/test_todo_visualization.py +0 -0
  325. {dulus-3.9.5 → dulus-3.10.2}/tests/test_tool_registry.py +0 -0
  326. {dulus-3.9.5 → dulus-3.10.2}/tests/test_tree_ls.py +0 -0
  327. {dulus-3.9.5 → dulus-3.10.2}/tests/test_voice.py +0 -0
  328. {dulus-3.9.5 → dulus-3.10.2}/tests/test_wire_events.py +0 -0
  329. {dulus-3.9.5 → dulus-3.10.2}/tmux_offloader.py +0 -0
  330. {dulus-3.9.5 → dulus-3.10.2}/tmux_tools.py +0 -0
  331. {dulus-3.9.5 → dulus-3.10.2}/tool_registry.py +0 -0
  332. {dulus-3.9.5 → dulus-3.10.2}/tools.py +0 -0
  333. {dulus-3.9.5 → dulus-3.10.2}/ui/__init__.py +0 -0
  334. {dulus-3.9.5 → dulus-3.10.2}/ui/input.py +0 -0
  335. {dulus-3.9.5 → dulus-3.10.2}/ui/render.py +0 -0
  336. {dulus-3.9.5 → dulus-3.10.2}/voice/__init__.py +0 -0
  337. {dulus-3.9.5 → dulus-3.10.2}/voice/audio_utils.py +0 -0
  338. {dulus-3.9.5 → dulus-3.10.2}/voice/keyterms.py +0 -0
  339. {dulus-3.9.5 → dulus-3.10.2}/voice/recorder.py +0 -0
  340. {dulus-3.9.5 → dulus-3.10.2}/voice/stt.py +0 -0
  341. {dulus-3.9.5 → dulus-3.10.2}/voice/tts.py +0 -0
  342. {dulus-3.9.5 → dulus-3.10.2}/voice/wake_word.py +0 -0
  343. {dulus-3.9.5 → dulus-3.10.2}/webbridge/__init__.py +0 -0
  344. {dulus-3.9.5 → dulus-3.10.2}/webbridge/core.py +0 -0
  345. {dulus-3.9.5 → dulus-3.10.2}/webbridge/tools.py +0 -0
  346. {dulus-3.9.5 → dulus-3.10.2}/webchat.py +0 -0
  347. {dulus-3.9.5 → dulus-3.10.2}/welcome.py +0 -0
@@ -6,5 +6,6 @@ recursive-include gui/dulus_orchestrator/dist *
6
6
  exclude gui/dulus_orchestrator/dist/runtime-state.js
7
7
  exclude gui/dulus_orchestrator/dist/desktop-pet.html
8
8
  recursive-include webbridge *
9
+ recursive-include webchat_ui *.html *.css *.js
9
10
  global-exclude __pycache__/*
10
11
  global-exclude *.pyc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dulus
3
- Version: 3.9.5
3
+ Version: 3.10.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
@@ -162,6 +162,8 @@ def run(
162
162
  tool_schemas=get_tool_schemas(),
163
163
  config=config,
164
164
  )):
165
+ if cancel_check and cancel_check():
166
+ return
165
167
  if isinstance(event, (TextChunk, ThinkingChunk)):
166
168
  yield event
167
169
  elif isinstance(event, AssistantTurn):
@@ -217,6 +219,8 @@ def run(
217
219
 
218
220
  # ── Execute tools ────────────────────────────────────────────────
219
221
  for tc in assistant_turn.tool_calls:
222
+ if cancel_check and cancel_check():
223
+ return
220
224
  yield ToolStart(tc["name"], tc["input"])
221
225
 
222
226
  # ── Governance gate (capabilities + pre_tool hook) ───────────────
@@ -29,7 +29,6 @@ silently ignored; telemetry must never slow down or break Dulus.
29
29
  from __future__ import annotations
30
30
 
31
31
  import base64
32
- import datetime as _dt
33
32
  import json
34
33
  import os
35
34
  import platform
@@ -46,23 +45,6 @@ MP_TOKEN = os.environ.get("DULUS_MP_TOKEN", "966ce8f5ccb32f06788f51be9f8bf8f5")
46
45
 
47
46
  _MP_ENDPOINT = "https://api.mixpanel.com/track"
48
47
 
49
- # ── PostHog (product analytics, runs alongside Mixpanel) ────────────────────
50
- # Project API keys (phc_...) are public/write-only by design — meant to ship in
51
- # client code, cannot read data. Override with DULUS_POSTHOG_KEY.
52
- POSTHOG_KEY = os.environ.get(
53
- "DULUS_POSTHOG_KEY", "phc_yqAJ42vbj8YFkdpxZicsS4asdbWFTPy6orR8uRNewfYD"
54
- )
55
- _POSTHOG_ENDPOINT = os.environ.get(
56
- "DULUS_POSTHOG_HOST", "https://us.i.posthog.com"
57
- ).rstrip("/") + "/capture/"
58
-
59
- # ── Amplitude (behavioral analytics, runs alongside the others) ─────────────
60
- # API keys are write-only client keys (safe to ship). Override w/ DULUS_AMPLITUDE_KEY.
61
- AMPLITUDE_KEY = os.environ.get(
62
- "DULUS_AMPLITUDE_KEY", "2511c9e1fc597b8bc6b81f193cc615a2"
63
- )
64
- _AMPLITUDE_ENDPOINT = "https://api2.amplitude.com/2/httpapi"
65
-
66
48
  # Populated by init_telemetry(); None = not initialised / disabled.
67
49
  _distinct_id: str | None = None
68
50
  _enabled: bool = False
@@ -139,11 +121,10 @@ def track(event: str, properties: dict | None = None) -> None:
139
121
  }
140
122
 
141
123
  def _send() -> None:
142
- from urllib.request import Request, urlopen
143
- from urllib.parse import urlencode
144
-
145
- # Mixpanel
146
124
  try:
125
+ from urllib.request import Request, urlopen
126
+ from urllib.parse import urlencode
127
+
147
128
  data = urlencode(
148
129
  {"data": base64.b64encode(json.dumps([payload]).encode()).decode()}
149
130
  ).encode()
@@ -152,55 +133,6 @@ def track(event: str, properties: dict | None = None) -> None:
152
133
  except Exception:
153
134
  pass # telemetry must never break Dulus
154
135
 
155
- # PostHog (same event, product-analytics brain)
156
- if POSTHOG_KEY:
157
- try:
158
- ph_props = {
159
- "dulus_version": _dulus_version,
160
- "os": platform.system(),
161
- "python": f"{sys.version_info.major}.{sys.version_info.minor}",
162
- "$process_person_profile": False, # anonymous, no PII person
163
- **(properties or {}),
164
- }
165
- ph_payload = {
166
- "api_key": POSTHOG_KEY,
167
- "event": event,
168
- "distinct_id": _distinct_id,
169
- "properties": ph_props,
170
- "timestamp": _dt.datetime.utcnow().isoformat() + "Z",
171
- }
172
- ph_data = json.dumps(ph_payload).encode()
173
- ph_req = Request(
174
- _POSTHOG_ENDPOINT, data=ph_data, method="POST",
175
- headers={"Content-Type": "application/json"},
176
- )
177
- urlopen(ph_req, timeout=4).read()
178
- except Exception:
179
- pass # telemetry must never break Dulus
180
-
181
- # Amplitude (same event, behavioral-analytics brain)
182
- if AMPLITUDE_KEY:
183
- try:
184
- amp_event = {
185
- "user_id": (_distinct_id or "anon").ljust(5, "_"), # min 5 chars
186
- "event_type": event,
187
- "platform": platform.system(),
188
- "app_version": _dulus_version,
189
- "event_properties": (properties or {}),
190
- }
191
- amp_payload = {
192
- "api_key": AMPLITUDE_KEY,
193
- "events": [amp_event],
194
- }
195
- amp_data = json.dumps(amp_payload).encode()
196
- amp_req = Request(
197
- _AMPLITUDE_ENDPOINT, data=amp_data, method="POST",
198
- headers={"Content-Type": "application/json"},
199
- )
200
- urlopen(amp_req, timeout=4).read()
201
- except Exception:
202
- pass # telemetry must never break Dulus
203
-
204
136
  threading.Thread(target=_send, daemon=True, name="telemetry").start()
205
137
 
206
138
 
@@ -0,0 +1,529 @@
1
+ """Display Blocks System - Visual representations of agent actions.
2
+
3
+ Provides a structured way to represent agent actions visually across multiple
4
+ frontends: CLI (dulus.py), WebChat (webchat_server.py), and Telegram.
5
+
6
+ Display blocks are dict-based and can be rendered in various formats.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import difflib
11
+ import html
12
+ from typing import Literal
13
+
14
+
15
+ # ── Display block type hints ─────────────────────────────────────────────────
16
+
17
+ # These are the supported display block types
18
+ DisplayBlockType = Literal[
19
+ "diff",
20
+ "todo",
21
+ "shell",
22
+ "background_task",
23
+ "think",
24
+ "code",
25
+ "table",
26
+ "error",
27
+ ]
28
+
29
+
30
+ # ── CLI Renderer ─────────────────────────────────────────────────────────────
31
+
32
+
33
+ class DisplayBlockRenderer:
34
+ """Render display blocks in various formats for different frontends.
35
+
36
+ Supports CLI (terminal), HTML (WebChat), and Telegram (Markdown) output formats.
37
+ Each render method takes a display block dict and returns a formatted string.
38
+ """
39
+
40
+ # ── Public API ──────────────────────────────────────────────────────────
41
+
42
+ @staticmethod
43
+ def render_cli(block: dict) -> str:
44
+ """Render a display block for CLI (terminal) output.
45
+
46
+ Args:
47
+ block: Display block dict with at least a 'type' key.
48
+
49
+ Returns:
50
+ Formatted string suitable for terminal display.
51
+ """
52
+ block_type = block.get("type", "unknown")
53
+ renderer = _CLI_RENDERERS.get(block_type, _CLI_RENDERERS["unknown"])
54
+ return renderer(block)
55
+
56
+ @staticmethod
57
+ def render_html(block: dict) -> str:
58
+ """Render a display block as HTML for WebChat.
59
+
60
+ Args:
61
+ block: Display block dict with at least a 'type' key.
62
+
63
+ Returns:
64
+ HTML string suitable for web display.
65
+ """
66
+ block_type = block.get("type", "unknown")
67
+ renderer = _HTML_RENDERERS.get(block_type, _HTML_RENDERERS["unknown"])
68
+ return renderer(block)
69
+
70
+ @staticmethod
71
+ def render_telegram(block: dict) -> str:
72
+ """Render a display block for Telegram (Markdown V2).
73
+
74
+ Args:
75
+ block: Display block dict with at least a 'type' key.
76
+
77
+ Returns:
78
+ Formatted string using Telegram Markdown V2 syntax.
79
+ """
80
+ block_type = block.get("type", "unknown")
81
+ renderer = _TG_RENDERERS.get(block_type, _TG_RENDERERS["unknown"])
82
+ return renderer(block)
83
+
84
+ @staticmethod
85
+ def render(block: dict, format: Literal["cli", "html", "telegram"] = "cli") -> str:
86
+ """Render a display block in the specified format.
87
+
88
+ Args:
89
+ block: Display block dict with at least a 'type' key.
90
+ format: Target format - "cli", "html", or "telegram".
91
+
92
+ Returns:
93
+ Formatted string in the requested format.
94
+ """
95
+ if format == "cli":
96
+ return DisplayBlockRenderer.render_cli(block)
97
+ elif format == "html":
98
+ return DisplayBlockRenderer.render_html(block)
99
+ elif format == "telegram":
100
+ return DisplayBlockRenderer.render_telegram(block)
101
+ return str(block)
102
+
103
+
104
+ # ── CLI Renderers ────────────────────────────────────────────────────────────
105
+
106
+
107
+ def _render_diff_cli(block: dict) -> str:
108
+ """Render a diff display block for CLI.
109
+
110
+ Shows a unified diff between old and new text.
111
+ """
112
+ path = block.get("path", "<unknown>")
113
+ old_text = block.get("old_text", "")
114
+ new_text = block.get("new_text", "")
115
+ old_start = block.get("old_start", 1)
116
+ new_start = block.get("new_start", 1)
117
+ is_summary = block.get("is_summary", False)
118
+
119
+ if is_summary:
120
+ return f"[Diff: {path}] (summary)"
121
+
122
+ old_lines = old_text.splitlines(keepends=True)
123
+ new_lines = new_text.splitlines(keepends=True)
124
+
125
+ # Generate unified diff
126
+ diff = difflib.unified_diff(
127
+ old_lines,
128
+ new_lines,
129
+ fromfile=f"a/{path}",
130
+ tofile=f"b/{path}",
131
+ lineterm="",
132
+ )
133
+ diff_text = "\n".join(diff)
134
+
135
+ if not diff_text:
136
+ return f"[Diff: {path}] No changes"
137
+
138
+ return f"[Diff: {path}]\n{diff_text}"
139
+
140
+
141
+ def _render_todo_cli(block: dict) -> str:
142
+ """Render a todo display block for CLI."""
143
+ items = block.get("items", [])
144
+ if not items:
145
+ return "[Todo] No items"
146
+
147
+ lines = ["Todo List:"]
148
+ icons = {"pending": "[ ]", "in_progress": "[/]", "done": "[x]"}
149
+ for item in items:
150
+ status = item.get("status", "pending")
151
+ icon = icons.get(status, "[ ]")
152
+ lines.append(f" {icon} {item.get('title', '')}")
153
+ return "\n".join(lines)
154
+
155
+
156
+ def _render_shell_cli(block: dict) -> str:
157
+ """Render a shell command display block for CLI."""
158
+ command = block.get("command", "")
159
+ language = block.get("language", "bash")
160
+ return f"$ {command}"
161
+
162
+
163
+ def _render_bg_task_cli(block: dict) -> str:
164
+ """Render a background task display block for CLI."""
165
+ task_id = block.get("task_id", "?")
166
+ kind = block.get("kind", "?")
167
+ status = block.get("status", "?")
168
+ description = block.get("description", "")
169
+
170
+ icons = {
171
+ "running": "●",
172
+ "completed": "✓",
173
+ "failed": "✗",
174
+ "stopped": "■",
175
+ }
176
+ icon = icons.get(status, "?")
177
+ return f"{icon} [{task_id}] {description} ({kind}, {status})"
178
+
179
+
180
+ def _render_think_cli(block: dict) -> str:
181
+ """Render a think display block for CLI."""
182
+ thought = block.get("thought", "")
183
+ lines = thought.splitlines()
184
+ # Indent thought content for visual distinction
185
+ indented = "\n".join(f" | {line}" for line in lines)
186
+ return f"[Thinking...]\n{indented}"
187
+
188
+
189
+ def _render_code_cli(block: dict) -> str:
190
+ """Render a code display block for CLI."""
191
+ code = block.get("code", "")
192
+ language = block.get("language", "")
193
+ header = f"```{language}" if language else "```"
194
+ return f"{header}\n{code}\n```"
195
+
196
+
197
+ def _render_table_cli(block: dict) -> str:
198
+ """Render a table display block for CLI."""
199
+ headers = block.get("headers", [])
200
+ rows = block.get("rows", [])
201
+ if not headers and not rows:
202
+ return "[Table] Empty"
203
+
204
+ # Simple text table rendering
205
+ all_rows = [headers] + rows if headers else rows
206
+ # Calculate column widths
207
+ col_count = max(len(r) for r in all_rows) if all_rows else 0
208
+ widths = [0] * col_count
209
+ for row in all_rows:
210
+ for i, cell in enumerate(row):
211
+ widths[i] = max(widths[i], len(str(cell)))
212
+
213
+ def _fmt_row(row: list) -> str:
214
+ cells = [str(cell).ljust(widths[i]) for i, cell in enumerate(row)]
215
+ return " | ".join(cells)
216
+
217
+ lines = []
218
+ if headers:
219
+ lines.append(_fmt_row(headers))
220
+ lines.append("-" * (sum(widths) + 3 * (col_count - 1)))
221
+ for row in rows:
222
+ lines.append(_fmt_row(row))
223
+ return "\n".join(lines)
224
+
225
+
226
+ def _render_error_cli(block: dict) -> str:
227
+ """Render an error display block for CLI."""
228
+ message = block.get("message", "Unknown error")
229
+ return f"[Error] {message}"
230
+
231
+
232
+ def _render_unknown_cli(block: dict) -> str:
233
+ """Render an unknown display block type for CLI."""
234
+ return f"[Display Block: {block.get('type', 'unknown')}]\n{str(block)}"
235
+
236
+
237
+ _CLI_RENDERERS: dict[str, callable] = {
238
+ "diff": _render_diff_cli,
239
+ "todo": _render_todo_cli,
240
+ "shell": _render_shell_cli,
241
+ "background_task": _render_bg_task_cli,
242
+ "think": _render_think_cli,
243
+ "code": _render_code_cli,
244
+ "table": _render_table_cli,
245
+ "error": _render_error_cli,
246
+ "unknown": _render_unknown_cli,
247
+ }
248
+
249
+
250
+ # ── HTML Renderers ───────────────────────────────────────────────────────────
251
+
252
+
253
+ def _render_diff_html(block: dict) -> str:
254
+ """Render a diff display block as HTML."""
255
+ path = html.escape(block.get("path", "<unknown>"))
256
+ old_text = html.escape(block.get("old_text", ""))
257
+ new_text = html.escape(block.get("new_text", ""))
258
+
259
+ return (
260
+ f'<div class="display-block diff-block">\n'
261
+ f' <div class="diff-header">{path}</div>\n'
262
+ f' <pre class="diff-old"><code>{old_text}</code></pre>\n'
263
+ f' <pre class="diff-new"><code>{new_text}</code></pre>\n'
264
+ f"</div>"
265
+ )
266
+
267
+
268
+ def _render_todo_html(block: dict) -> str:
269
+ """Render a todo display block as HTML."""
270
+ items = block.get("items", [])
271
+ if not items:
272
+ return '<div class="display-block todo-block"><p>No items</p></div>'
273
+
274
+ lines = ['<div class="display-block todo-block"><ul>']
275
+ icons = {
276
+ "pending": "⬜",
277
+ "in_progress": "🔄",
278
+ "done": "✅",
279
+ }
280
+ for item in items:
281
+ status = item.get("status", "pending")
282
+ icon = icons.get(status, "⬜")
283
+ title = html.escape(item.get("title", ""))
284
+ css_class = f"todo-{status}"
285
+ lines.append(f' <li class="{css_class}">{icon} {title}</li>')
286
+ lines.append("</ul></div>")
287
+ return "\n".join(lines)
288
+
289
+
290
+ def _render_shell_html(block: dict) -> str:
291
+ """Render a shell command display block as HTML."""
292
+ command = html.escape(block.get("command", ""))
293
+ language = html.escape(block.get("language", "bash"))
294
+ return (
295
+ f'<div class="display-block shell-block">\n'
296
+ f' <pre><code class="language-{language}">${command}</code></pre>\n'
297
+ f"</div>"
298
+ )
299
+
300
+
301
+ def _render_bg_task_html(block: dict) -> str:
302
+ """Render a background task display block as HTML."""
303
+ task_id = html.escape(block.get("task_id", "?"))
304
+ kind = html.escape(block.get("kind", "?"))
305
+ status = html.escape(block.get("status", "?"))
306
+ description = html.escape(block.get("description", ""))
307
+
308
+ status_colors = {
309
+ "running": "#2196F3",
310
+ "completed": "#4CAF50",
311
+ "failed": "#f44336",
312
+ "stopped": "#FF9800",
313
+ }
314
+ color = status_colors.get(status, "#9E9E9E")
315
+
316
+ return (
317
+ f'<div class="display-block bg-task-block">\n'
318
+ f' <span class="task-id">[{task_id}]</span>\n'
319
+ f' <span class="task-desc">{description}</span>\n'
320
+ f' <span class="task-kind">({kind})</span>\n'
321
+ f' <span class="task-status" style="color: {color};">{status}</span>\n'
322
+ f"</div>"
323
+ )
324
+
325
+
326
+ def _render_think_html(block: dict) -> str:
327
+ """Render a think display block as HTML."""
328
+ thought = html.escape(block.get("thought", ""))
329
+ return (
330
+ f'<div class="display-block think-block">\n'
331
+ f' <div class="think-header">💭 Thinking</div>\n'
332
+ f' <pre class="think-content">{thought}</pre>\n'
333
+ f"</div>"
334
+ )
335
+
336
+
337
+ def _render_code_html(block: dict) -> str:
338
+ """Render a code display block as HTML."""
339
+ code = html.escape(block.get("code", ""))
340
+ language = html.escape(block.get("language", ""))
341
+ return (
342
+ f'<div class="display-block code-block">\n'
343
+ f' <pre><code class="language-{language}">{code}</code></pre>\n'
344
+ f"</div>"
345
+ )
346
+
347
+
348
+ def _render_table_html(block: dict) -> str:
349
+ """Render a table display block as HTML."""
350
+ headers = block.get("headers", [])
351
+ rows = block.get("rows", [])
352
+ if not headers and not rows:
353
+ return '<div class="display-block table-block"><p>Empty table</p></div>'
354
+
355
+ lines = ['<div class="display-block table-block">', "<table>"]
356
+ if headers:
357
+ lines.append(" <tr>" + "".join(f"<th>{html.escape(str(h))}</th>" for h in headers) + "</tr>")
358
+ for row in rows:
359
+ lines.append(" <tr>" + "".join(f"<td>{html.escape(str(c))}</td>" for c in row) + "</tr>")
360
+ lines.extend(["</table>", "</div>"])
361
+ return "\n".join(lines)
362
+
363
+
364
+ def _render_error_html(block: dict) -> str:
365
+ """Render an error display block as HTML."""
366
+ message = html.escape(block.get("message", "Unknown error"))
367
+ return (
368
+ f'<div class="display-block error-block" style="color: #f44336;">\n'
369
+ f' <strong>Error:</strong> {message}\n'
370
+ f"</div>"
371
+ )
372
+
373
+
374
+ def _render_unknown_html(block: dict) -> str:
375
+ """Render an unknown display block type as HTML."""
376
+ content = html.escape(str(block))
377
+ return f'<div class="display-block unknown-block"><pre>{content}</pre></div>'
378
+
379
+
380
+ _HTML_RENDERERS: dict[str, callable] = {
381
+ "diff": _render_diff_html,
382
+ "todo": _render_todo_html,
383
+ "shell": _render_shell_html,
384
+ "background_task": _render_bg_task_html,
385
+ "think": _render_think_html,
386
+ "code": _render_code_html,
387
+ "table": _render_table_html,
388
+ "error": _render_error_html,
389
+ "unknown": _render_unknown_html,
390
+ }
391
+
392
+
393
+ # ── Telegram Renderers ───────────────────────────────────────────────────────
394
+
395
+
396
+ def _render_diff_telegram(block: dict) -> str:
397
+ """Render a diff display block for Telegram (Markdown V2).
398
+
399
+ Note: Telegram has limited formatting for diffs, so we keep it simple.
400
+ """
401
+ path = block.get("path", "<unknown>")
402
+ old_text = block.get("old_text", "")
403
+ new_text = block.get("new_text", "")
404
+
405
+ # Escape Telegram Markdown V2 special chars
406
+ def _esc(text: str) -> str:
407
+ for c in "_[]()~`>#+-=|{}.!":
408
+ text = text.replace(c, f"\\{c}")
409
+ return text
410
+
411
+ return f"📝 *{_esc(path)}*\n```diff\n- {_esc('Old')}\n+ {_esc('New')}\n```"
412
+
413
+
414
+ def _render_todo_telegram(block: dict) -> str:
415
+ """Render a todo display block for Telegram (Markdown V2)."""
416
+ items = block.get("items", [])
417
+ if not items:
418
+ return "📋 *Todo List:* Empty"
419
+
420
+ def _esc(text: str) -> str:
421
+ for c in "_[]()~`>#+-=|{}.!":
422
+ text = text.replace(c, f"\\{c}")
423
+ return text
424
+
425
+ icons = {
426
+ "pending": "⬜",
427
+ "in_progress": "🔄",
428
+ "done": "✅",
429
+ }
430
+ lines = ["📋 *Todo List:*"]
431
+ for item in items:
432
+ status = item.get("status", "pending")
433
+ icon = icons.get(status, "⬜")
434
+ title = _esc(item.get("title", ""))
435
+ lines.append(f"{icon} {title}")
436
+ return "\n".join(lines)
437
+
438
+
439
+ def _render_shell_telegram(block: dict) -> str:
440
+ """Render a shell command display block for Telegram."""
441
+ command = block.get("command", "")
442
+ return f"```bash\n$ {command}\n```"
443
+
444
+
445
+ def _render_bg_task_telegram(block: dict) -> str:
446
+ """Render a background task display block for Telegram."""
447
+ task_id = block.get("task_id", "?")
448
+ kind = block.get("kind", "?")
449
+ status = block.get("status", "?")
450
+ description = block.get("description", "")
451
+
452
+ icons = {
453
+ "running": "🔄",
454
+ "completed": "✅",
455
+ "failed": "❌",
456
+ "stopped": "🛑",
457
+ }
458
+ icon = icons.get(status, "❓")
459
+
460
+ def _esc(text: str) -> str:
461
+ for c in "_[]()~`>#+-=|{}.!":
462
+ text = text.replace(c, f"\\{c}")
463
+ return text
464
+
465
+ return f"{icon} *Task `{task_id}`*\n{_esc(description)}\n({_esc(kind)}, {_esc(status)})"
466
+
467
+
468
+ def _render_think_telegram(block: dict) -> str:
469
+ """Render a think display block for Telegram."""
470
+ thought = block.get("thought", "")
471
+ return f"💭 *Thinking*\n```\n{thought[:3000]}\n```"
472
+
473
+
474
+ def _render_code_telegram(block: dict) -> str:
475
+ """Render a code display block for Telegram."""
476
+ code = block.get("code", "")
477
+ language = block.get("language", "")
478
+ return f"```{language}\n{code[:3000]}\n```"
479
+
480
+
481
+ def _render_table_telegram(block: dict) -> str:
482
+ """Render a table display block for Telegram."""
483
+ headers = block.get("headers", [])
484
+ rows = block.get("rows", [])
485
+ if not headers and not rows:
486
+ return "📊 *Table:* Empty"
487
+
488
+ def _esc(text: str) -> str:
489
+ for c in "_[]()~`>#+-=|{}.!":
490
+ text = text.replace(c, f"\\{c}")
491
+ return text
492
+
493
+ lines = []
494
+ if headers:
495
+ lines.append(" | ".join(_esc(str(h)) for h in headers))
496
+ lines.append("—" * 20)
497
+ for row in rows:
498
+ lines.append(" | ".join(_esc(str(c)) for c in row))
499
+ return "\n".join(lines)
500
+
501
+
502
+ def _render_error_telegram(block: dict) -> str:
503
+ """Render an error display block for Telegram."""
504
+ message = block.get("message", "Unknown error")
505
+
506
+ def _esc(text: str) -> str:
507
+ for c in "_[]()~`>#+-=|{}.!":
508
+ text = text.replace(c, f"\\{c}")
509
+ return text
510
+
511
+ return f"❌ *Error:* {_esc(message)}"
512
+
513
+
514
+ def _render_unknown_telegram(block: dict) -> str:
515
+ """Render an unknown display block type for Telegram."""
516
+ return f"```\n{str(block)[:1000]}\n```"
517
+
518
+
519
+ _TG_RENDERERS: dict[str, callable] = {
520
+ "diff": _render_diff_telegram,
521
+ "todo": _render_todo_telegram,
522
+ "shell": _render_shell_telegram,
523
+ "background_task": _render_bg_task_telegram,
524
+ "think": _render_think_telegram,
525
+ "code": _render_code_telegram,
526
+ "table": _render_table_telegram,
527
+ "error": _render_error_telegram,
528
+ "unknown": _render_unknown_telegram,
529
+ }