dulus 0.2.26__tar.gz → 0.2.28__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 (164) hide show
  1. {dulus-0.2.26/dulus.egg-info → dulus-0.2.28}/PKG-INFO +2 -2
  2. {dulus-0.2.26 → dulus-0.2.28}/README.md +1 -1
  3. {dulus-0.2.26 → dulus-0.2.28}/docs/news.md +4 -0
  4. {dulus-0.2.26 → dulus-0.2.28/dulus.egg-info}/PKG-INFO +2 -2
  5. {dulus-0.2.26 → dulus-0.2.28}/dulus.py +39 -2
  6. {dulus-0.2.26 → dulus-0.2.28}/pyproject.toml +1 -1
  7. {dulus-0.2.26 → dulus-0.2.28}/LICENSE +0 -0
  8. {dulus-0.2.26 → dulus-0.2.28}/MANIFEST.in +0 -0
  9. {dulus-0.2.26 → dulus-0.2.28}/agent.py +0 -0
  10. {dulus-0.2.26 → dulus-0.2.28}/backend/__init__.py +0 -0
  11. {dulus-0.2.26 → dulus-0.2.28}/backend/compressor.py +0 -0
  12. {dulus-0.2.26 → dulus-0.2.28}/backend/context.py +0 -0
  13. {dulus-0.2.26 → dulus-0.2.28}/backend/githook.py +0 -0
  14. {dulus-0.2.26 → dulus-0.2.28}/backend/marketplace.py +0 -0
  15. {dulus-0.2.26 → dulus-0.2.28}/backend/mempalace_bridge.py +0 -0
  16. {dulus-0.2.26 → dulus-0.2.28}/backend/personas.py +0 -0
  17. {dulus-0.2.26 → dulus-0.2.28}/backend/plugins.py +0 -0
  18. {dulus-0.2.26 → dulus-0.2.28}/backend/server.py +0 -0
  19. {dulus-0.2.26 → dulus-0.2.28}/backend/tasks.py +0 -0
  20. {dulus-0.2.26 → dulus-0.2.28}/batch_api.py +0 -0
  21. {dulus-0.2.26 → dulus-0.2.28}/checkpoint/__init__.py +0 -0
  22. {dulus-0.2.26 → dulus-0.2.28}/checkpoint/hooks.py +0 -0
  23. {dulus-0.2.26 → dulus-0.2.28}/checkpoint/store.py +0 -0
  24. {dulus-0.2.26 → dulus-0.2.28}/checkpoint/types.py +0 -0
  25. {dulus-0.2.26 → dulus-0.2.28}/claude_code_watcher.py +0 -0
  26. {dulus-0.2.26 → dulus-0.2.28}/clipboard_utils.py +0 -0
  27. {dulus-0.2.26 → dulus-0.2.28}/cloudsave.py +0 -0
  28. {dulus-0.2.26 → dulus-0.2.28}/common.py +0 -0
  29. {dulus-0.2.26 → dulus-0.2.28}/compaction.py +0 -0
  30. {dulus-0.2.26 → dulus-0.2.28}/config.py +0 -0
  31. {dulus-0.2.26 → dulus-0.2.28}/context.py +0 -0
  32. {dulus-0.2.26 → dulus-0.2.28}/data/__init__.py +0 -0
  33. {dulus-0.2.26 → dulus-0.2.28}/data/active_persona.json +0 -0
  34. {dulus-0.2.26 → dulus-0.2.28}/data/context.json +0 -0
  35. {dulus-0.2.26 → dulus-0.2.28}/data/marketplace.json +0 -0
  36. {dulus-0.2.26 → dulus-0.2.28}/data/personas.json +0 -0
  37. {dulus-0.2.26 → dulus-0.2.28}/data/plugins/__init__.py +0 -0
  38. {dulus-0.2.26 → dulus-0.2.28}/data/plugins/composio/__init__.py +0 -0
  39. {dulus-0.2.26 → dulus-0.2.28}/data/plugins/composio/composio_plugin/__init__.py +0 -0
  40. {dulus-0.2.26 → dulus-0.2.28}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
  41. {dulus-0.2.26 → dulus-0.2.28}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
  42. {dulus-0.2.26 → dulus-0.2.28}/data/plugins/composio/plugin.json +0 -0
  43. {dulus-0.2.26 → dulus-0.2.28}/data/plugins/composio/plugin_tool.py +0 -0
  44. {dulus-0.2.26 → dulus-0.2.28}/data/tasks.json +0 -0
  45. {dulus-0.2.26 → dulus-0.2.28}/docs/README.md +0 -0
  46. {dulus-0.2.26 → dulus-0.2.28}/docs/__init__.py +0 -0
  47. {dulus-0.2.26 → dulus-0.2.28}/docs/api.html +0 -0
  48. {dulus-0.2.26 → dulus-0.2.28}/docs/architecture.md +0 -0
  49. {dulus-0.2.26 → dulus-0.2.28}/docs/azure-speech-template.json +0 -0
  50. {dulus-0.2.26 → dulus-0.2.28}/docs/dashboard/index.html +0 -0
  51. {dulus-0.2.26 → dulus-0.2.28}/docs/divider.svg +0 -0
  52. {dulus-0.2.26 → dulus-0.2.28}/docs/generate.py +0 -0
  53. {dulus-0.2.26 → dulus-0.2.28}/docs/hero.svg +0 -0
  54. {dulus-0.2.26 → dulus-0.2.28}/docs/index.html +0 -0
  55. {dulus-0.2.26 → dulus-0.2.28}/docs/nvidia-models.svg +0 -0
  56. {dulus-0.2.26 → dulus-0.2.28}/docs/particle-playground.html +0 -0
  57. {dulus-0.2.26 → dulus-0.2.28}/docs/personas/index.html +0 -0
  58. {dulus-0.2.26 → dulus-0.2.28}/docs/poetry-banner.png +0 -0
  59. {dulus-0.2.26 → dulus-0.2.28}/docs/preview.html +0 -0
  60. {dulus-0.2.26 → dulus-0.2.28}/docs/sec-agents.svg +0 -0
  61. {dulus-0.2.26 → dulus-0.2.28}/docs/sec-brainstorm.svg +0 -0
  62. {dulus-0.2.26 → dulus-0.2.28}/docs/sec-bridges.svg +0 -0
  63. {dulus-0.2.26 → dulus-0.2.28}/docs/sec-features.svg +0 -0
  64. {dulus-0.2.26 → dulus-0.2.28}/docs/sec-freetier.svg +0 -0
  65. {dulus-0.2.26 → dulus-0.2.28}/docs/sec-memory.svg +0 -0
  66. {dulus-0.2.26 → dulus-0.2.28}/docs/sec-models.svg +0 -0
  67. {dulus-0.2.26 → dulus-0.2.28}/docs/sec-perms.svg +0 -0
  68. {dulus-0.2.26 → dulus-0.2.28}/docs/sec-plugins.svg +0 -0
  69. {dulus-0.2.26 → dulus-0.2.28}/docs/sec-quickstart.svg +0 -0
  70. {dulus-0.2.26 → dulus-0.2.28}/docs/sec-ssj.svg +0 -0
  71. {dulus-0.2.26 → dulus-0.2.28}/docs/spinners.svg +0 -0
  72. {dulus-0.2.26 → dulus-0.2.28}/docs/split-pane.svg +0 -0
  73. {dulus-0.2.26 → dulus-0.2.28}/docs/terminal-boot.svg +0 -0
  74. {dulus-0.2.26 → dulus-0.2.28}/docs/uploads/particle-playground.html +0 -0
  75. {dulus-0.2.26 → dulus-0.2.28}/dulus.egg-info/SOURCES.txt +0 -0
  76. {dulus-0.2.26 → dulus-0.2.28}/dulus.egg-info/dependency_links.txt +0 -0
  77. {dulus-0.2.26 → dulus-0.2.28}/dulus.egg-info/entry_points.txt +0 -0
  78. {dulus-0.2.26 → dulus-0.2.28}/dulus.egg-info/requires.txt +0 -0
  79. {dulus-0.2.26 → dulus-0.2.28}/dulus.egg-info/top_level.txt +0 -0
  80. {dulus-0.2.26 → dulus-0.2.28}/dulus_gui.py +0 -0
  81. {dulus-0.2.26 → dulus-0.2.28}/dulus_mcp/__init__.py +0 -0
  82. {dulus-0.2.26 → dulus-0.2.28}/dulus_mcp/client.py +0 -0
  83. {dulus-0.2.26 → dulus-0.2.28}/dulus_mcp/config.py +0 -0
  84. {dulus-0.2.26 → dulus-0.2.28}/dulus_mcp/tools.py +0 -0
  85. {dulus-0.2.26 → dulus-0.2.28}/dulus_mcp/types.py +0 -0
  86. {dulus-0.2.26 → dulus-0.2.28}/gui/__init__.py +0 -0
  87. {dulus-0.2.26 → dulus-0.2.28}/gui/agent_bridge.py +0 -0
  88. {dulus-0.2.26 → dulus-0.2.28}/gui/chat_widget.py +0 -0
  89. {dulus-0.2.26 → dulus-0.2.28}/gui/main_window.py +0 -0
  90. {dulus-0.2.26 → dulus-0.2.28}/gui/personas.py +0 -0
  91. {dulus-0.2.26 → dulus-0.2.28}/gui/session_utils.py +0 -0
  92. {dulus-0.2.26 → dulus-0.2.28}/gui/settings_dialog.py +0 -0
  93. {dulus-0.2.26 → dulus-0.2.28}/gui/sidebar.py +0 -0
  94. {dulus-0.2.26 → dulus-0.2.28}/gui/tasks_view.py +0 -0
  95. {dulus-0.2.26 → dulus-0.2.28}/gui/themes.py +0 -0
  96. {dulus-0.2.26 → dulus-0.2.28}/gui/tool_panel.py +0 -0
  97. {dulus-0.2.26 → dulus-0.2.28}/input.py +0 -0
  98. {dulus-0.2.26 → dulus-0.2.28}/license_manager.py +0 -0
  99. {dulus-0.2.26 → dulus-0.2.28}/memory/__init__.py +0 -0
  100. {dulus-0.2.26 → dulus-0.2.28}/memory/audit.py +0 -0
  101. {dulus-0.2.26 → dulus-0.2.28}/memory/consolidator.py +0 -0
  102. {dulus-0.2.26 → dulus-0.2.28}/memory/context.py +0 -0
  103. {dulus-0.2.26 → dulus-0.2.28}/memory/offload.py +0 -0
  104. {dulus-0.2.26 → dulus-0.2.28}/memory/palace.py +0 -0
  105. {dulus-0.2.26 → dulus-0.2.28}/memory/scan.py +0 -0
  106. {dulus-0.2.26 → dulus-0.2.28}/memory/sessions.py +0 -0
  107. {dulus-0.2.26 → dulus-0.2.28}/memory/store.py +0 -0
  108. {dulus-0.2.26 → dulus-0.2.28}/memory/tools.py +0 -0
  109. {dulus-0.2.26 → dulus-0.2.28}/memory/types.py +0 -0
  110. {dulus-0.2.26 → dulus-0.2.28}/memory/vector_search.py +0 -0
  111. {dulus-0.2.26 → dulus-0.2.28}/multi_agent/__init__.py +0 -0
  112. {dulus-0.2.26 → dulus-0.2.28}/multi_agent/subagent.py +0 -0
  113. {dulus-0.2.26 → dulus-0.2.28}/multi_agent/tools.py +0 -0
  114. {dulus-0.2.26 → dulus-0.2.28}/offload_helper.py +0 -0
  115. {dulus-0.2.26 → dulus-0.2.28}/plugin/__init__.py +0 -0
  116. {dulus-0.2.26 → dulus-0.2.28}/plugin/autoadapter.py +0 -0
  117. {dulus-0.2.26 → dulus-0.2.28}/plugin/loader.py +0 -0
  118. {dulus-0.2.26 → dulus-0.2.28}/plugin/recommend.py +0 -0
  119. {dulus-0.2.26 → dulus-0.2.28}/plugin/store.py +0 -0
  120. {dulus-0.2.26 → dulus-0.2.28}/plugin/types.py +0 -0
  121. {dulus-0.2.26 → dulus-0.2.28}/providers.py +0 -0
  122. {dulus-0.2.26 → dulus-0.2.28}/setup.cfg +0 -0
  123. {dulus-0.2.26 → dulus-0.2.28}/skill/__init__.py +0 -0
  124. {dulus-0.2.26 → dulus-0.2.28}/skill/builtin.py +0 -0
  125. {dulus-0.2.26 → dulus-0.2.28}/skill/clawhub.py +0 -0
  126. {dulus-0.2.26 → dulus-0.2.28}/skill/executor.py +0 -0
  127. {dulus-0.2.26 → dulus-0.2.28}/skill/loader.py +0 -0
  128. {dulus-0.2.26 → dulus-0.2.28}/skill/tools.py +0 -0
  129. {dulus-0.2.26 → dulus-0.2.28}/skills.py +0 -0
  130. {dulus-0.2.26 → dulus-0.2.28}/spinner.py +0 -0
  131. {dulus-0.2.26 → dulus-0.2.28}/string_utils.py +0 -0
  132. {dulus-0.2.26 → dulus-0.2.28}/subagent.py +0 -0
  133. {dulus-0.2.26 → dulus-0.2.28}/task/__init__.py +0 -0
  134. {dulus-0.2.26 → dulus-0.2.28}/task/store.py +0 -0
  135. {dulus-0.2.26 → dulus-0.2.28}/task/tools.py +0 -0
  136. {dulus-0.2.26 → dulus-0.2.28}/task/types.py +0 -0
  137. {dulus-0.2.26 → dulus-0.2.28}/tests/test_checkpoint.py +0 -0
  138. {dulus-0.2.26 → dulus-0.2.28}/tests/test_compaction.py +0 -0
  139. {dulus-0.2.26 → dulus-0.2.28}/tests/test_diff_view.py +0 -0
  140. {dulus-0.2.26 → dulus-0.2.28}/tests/test_injection_fix.py +0 -0
  141. {dulus-0.2.26 → dulus-0.2.28}/tests/test_license.py +0 -0
  142. {dulus-0.2.26 → dulus-0.2.28}/tests/test_mcp.py +0 -0
  143. {dulus-0.2.26 → dulus-0.2.28}/tests/test_memory.py +0 -0
  144. {dulus-0.2.26 → dulus-0.2.28}/tests/test_plugin.py +0 -0
  145. {dulus-0.2.26 → dulus-0.2.28}/tests/test_skills.py +0 -0
  146. {dulus-0.2.26 → dulus-0.2.28}/tests/test_subagent.py +0 -0
  147. {dulus-0.2.26 → dulus-0.2.28}/tests/test_task.py +0 -0
  148. {dulus-0.2.26 → dulus-0.2.28}/tests/test_telegram_buffer.py +0 -0
  149. {dulus-0.2.26 → dulus-0.2.28}/tests/test_tool_registry.py +0 -0
  150. {dulus-0.2.26 → dulus-0.2.28}/tests/test_voice.py +0 -0
  151. {dulus-0.2.26 → dulus-0.2.28}/tmux_offloader.py +0 -0
  152. {dulus-0.2.26 → dulus-0.2.28}/tmux_tools.py +0 -0
  153. {dulus-0.2.26 → dulus-0.2.28}/tool_registry.py +0 -0
  154. {dulus-0.2.26 → dulus-0.2.28}/tools.py +0 -0
  155. {dulus-0.2.26 → dulus-0.2.28}/ui/__init__.py +0 -0
  156. {dulus-0.2.26 → dulus-0.2.28}/ui/input.py +0 -0
  157. {dulus-0.2.26 → dulus-0.2.28}/ui/render.py +0 -0
  158. {dulus-0.2.26 → dulus-0.2.28}/voice/__init__.py +0 -0
  159. {dulus-0.2.26 → dulus-0.2.28}/voice/keyterms.py +0 -0
  160. {dulus-0.2.26 → dulus-0.2.28}/voice/recorder.py +0 -0
  161. {dulus-0.2.26 → dulus-0.2.28}/voice/stt.py +0 -0
  162. {dulus-0.2.26 → dulus-0.2.28}/voice/tts.py +0 -0
  163. {dulus-0.2.26 → dulus-0.2.28}/webchat.py +0 -0
  164. {dulus-0.2.26 → dulus-0.2.28}/webchat_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dulus
3
- Version: 0.2.26
3
+ Version: 0.2.28
4
4
  Summary: Spanish-first multi-provider AI CLI — 14 NVIDIA models free, Mesa Redonda, voice, TTS, RTK token reducer, MemPalace
5
5
  Author: KevRojo
6
6
  License: GPL-3.0
@@ -69,7 +69,7 @@ SET /sticky_input ON since the first run for the best experience!
69
69
  <a href="https://pypi.org/project/dulus/"><img src="https://static.pepy.tech/badge/dulus?style=flat-square" alt="downloads"/></a>
70
70
  <img src="https://img.shields.io/badge/python-3.11+-ff6b1f?style=flat-square&labelColor=07070a" alt="python"/>
71
71
  <img src="https://img.shields.io/badge/license-GPLv3-ff6b1f?style=flat-square&labelColor=07070a" alt="license"/>
72
- <img src="https://img.shields.io/badge/version-v0.2.26-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
72
+ <img src="https://img.shields.io/badge/version-v0.2.28-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
73
73
  <img src="https://img.shields.io/badge/providers-11-ff6b1f?style=flat-square&labelColor=07070a" alt="providers"/>
74
74
  <img src="https://img.shields.io/badge/tools-27-ff6b1f?style=flat-square&labelColor=07070a" alt="tools"/>
75
75
  <img src="https://img.shields.io/badge/tests-263+-ff6b1f?style=flat-square&labelColor=07070a" alt="tests"/>
@@ -22,7 +22,7 @@ SET /sticky_input ON since the first run for the best experience!
22
22
  <a href="https://pypi.org/project/dulus/"><img src="https://static.pepy.tech/badge/dulus?style=flat-square" alt="downloads"/></a>
23
23
  <img src="https://img.shields.io/badge/python-3.11+-ff6b1f?style=flat-square&labelColor=07070a" alt="python"/>
24
24
  <img src="https://img.shields.io/badge/license-GPLv3-ff6b1f?style=flat-square&labelColor=07070a" alt="license"/>
25
- <img src="https://img.shields.io/badge/version-v0.2.26-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
25
+ <img src="https://img.shields.io/badge/version-v0.2.28-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
26
26
  <img src="https://img.shields.io/badge/providers-11-ff6b1f?style=flat-square&labelColor=07070a" alt="providers"/>
27
27
  <img src="https://img.shields.io/badge/tools-27-ff6b1f?style=flat-square&labelColor=07070a" alt="tools"/>
28
28
  <img src="https://img.shields.io/badge/tests-263+-ff6b1f?style=flat-square&labelColor=07070a" alt="tests"/>
@@ -3,6 +3,10 @@
3
3
  ## 🔥🔥🔥 News (Pacific Time)
4
4
 
5
5
 
6
+ - May 09, 2026 (**v0.2.28**): **IPC server thread no longer crashes on idle / dropped connections** — `conn.recv()` was hitting its 60s read timeout and raising `TimeoutError` out of the worker, killing the IPC thread silently. After that, the daemon was still running but `dulus "..."` from any shell would hang forever. Caught socket.timeout / ConnectionReset / BrokenPipe / OSError around the request-handling block so a single bad client can't take down the server.
7
+
8
+ - May 09, 2026 (**v0.2.27**): **`/bg` no longer leaves stale state** — when something else (a REPL, an old daemon) is already on `127.0.0.1:5151`, `/bg start` now refuses to spawn a duplicate that would just fail to bind, explains what's happening, and clears the stale PID file. `/bg status` self-heals: when it sees a stale PID it auto-removes it instead of complaining on every call. The previous "Some Dulus is listening on IPC, but our PID file is stale" warning is gone for good.
9
+
6
10
  - May 09, 2026 (**v0.2.26**): **`/bg start` daemon crash fix + defensive `clr()` + composio fallback**
7
11
  - **Daemon was silently crashing on launch.** `_run_daemon` printed its banner with `clr("...", "accent", "bold")`, but the default theme palette only ships {blue, cyan, gray, green, magenta, red, white, yellow} — `"accent"` raised `KeyError` and killed the process before the prompt loop started. WebChat + IPC threads, being daemon=True, died with it. Switched the banner to `"yellow"` and wrapped in try/except so a stale theme color name never takes the daemon down.
8
12
  - **`clr()` is now defensive.** Missing color keys are silently dropped instead of raising. One typo in a theme name no longer crashes the REPL.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dulus
3
- Version: 0.2.26
3
+ Version: 0.2.28
4
4
  Summary: Spanish-first multi-provider AI CLI — 14 NVIDIA models free, Mesa Redonda, voice, TTS, RTK token reducer, MemPalace
5
5
  Author: KevRojo
6
6
  License: GPL-3.0
@@ -69,7 +69,7 @@ SET /sticky_input ON since the first run for the best experience!
69
69
  <a href="https://pypi.org/project/dulus/"><img src="https://static.pepy.tech/badge/dulus?style=flat-square" alt="downloads"/></a>
70
70
  <img src="https://img.shields.io/badge/python-3.11+-ff6b1f?style=flat-square&labelColor=07070a" alt="python"/>
71
71
  <img src="https://img.shields.io/badge/license-GPLv3-ff6b1f?style=flat-square&labelColor=07070a" alt="license"/>
72
- <img src="https://img.shields.io/badge/version-v0.2.26-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
72
+ <img src="https://img.shields.io/badge/version-v0.2.28-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
73
73
  <img src="https://img.shields.io/badge/providers-11-ff6b1f?style=flat-square&labelColor=07070a" alt="providers"/>
74
74
  <img src="https://img.shields.io/badge/tools-27-ff6b1f?style=flat-square&labelColor=07070a" alt="tools"/>
75
75
  <img src="https://img.shields.io/badge/tests-263+-ff6b1f?style=flat-square&labelColor=07070a" alt="tests"/>
@@ -218,7 +218,7 @@ try:
218
218
  from importlib.metadata import version as _pkg_version
219
219
  VERSION = _pkg_version("dulus")
220
220
  except Exception:
221
- VERSION = "0.2.26" # dev fallback — keep in sync with pyproject.toml
221
+ VERSION = "0.2.28" # dev fallback — keep in sync with pyproject.toml
222
222
 
223
223
  # ── ANSI helpers (used even with rich for non-markdown output) ─────────────
224
224
  from common import C, clr, info, ok, warn, err, stream_thinking, print_tool_start, print_tool_end, sanitize_text
@@ -1581,9 +1581,22 @@ def cmd_bg(args: str, _state, config) -> bool:
1581
1581
  elif alive and not ipc:
1582
1582
  warn(f"PID {pid} alive but IPC port not responding (still booting?)")
1583
1583
  elif ipc:
1584
- warn("Some Dulus is listening on IPC, but our PID file is stale.")
1584
+ warn("Another Dulus (REPL or older daemon) is on the IPC port.")
1585
+ info(f" IPC: 127.0.0.1:{DULUS_IPC_PORT} (responding, but not our daemon)")
1586
+ info(" You can still reach it via `dulus \"...\"` from any shell.")
1587
+ info(" /bg start won't spawn a duplicate — kill the other one first if needed.")
1588
+ # Clear the stale PID file so this warning self-heals next time.
1589
+ try:
1590
+ BG_PID.unlink()
1591
+ except FileNotFoundError:
1592
+ pass
1585
1593
  else:
1586
1594
  info("Dulus background: NOT RUNNING")
1595
+ # Clean up any stale PID file just in case.
1596
+ try:
1597
+ BG_PID.unlink()
1598
+ except FileNotFoundError:
1599
+ pass
1587
1600
  return True
1588
1601
 
1589
1602
  # ── /bg stop ──────────────────────────────────────────────────────────
@@ -1639,6 +1652,21 @@ def cmd_bg(args: str, _state, config) -> bool:
1639
1652
  info(f"Already running (PID {existing_pid}). Use `/bg status` for details.")
1640
1653
  return True
1641
1654
 
1655
+ # Some other Dulus (a REPL on this machine, an old daemon, etc.) is
1656
+ # holding the IPC port. Spawning a new daemon would just fail to bind
1657
+ # and leave a stale PID. Better: tell the user what's up.
1658
+ if _ipc_alive():
1659
+ warn(f"Port {DULUS_IPC_PORT} is already in use by another Dulus process.")
1660
+ info("If that's a REPL you're running, this Dulus is already reachable")
1661
+ info(f" via `dulus \"...\"` from any shell — no /bg start needed.")
1662
+ info("If it's a stale daemon, find and kill it manually, then retry.")
1663
+ # Clean up the stale PID file so /bg status stops complaining.
1664
+ try:
1665
+ BG_PID.unlink()
1666
+ except FileNotFoundError:
1667
+ pass
1668
+ return True
1669
+
1642
1670
  # Parse --web-port
1643
1671
  web_port = config.get("_webchat_port", 5000)
1644
1672
  if "--web-port" in parts:
@@ -4052,6 +4080,15 @@ def _ipc_server_loop(config, state):
4052
4080
  conn.sendall(payload)
4053
4081
  except Exception:
4054
4082
  pass
4083
+ except (_socket.timeout, TimeoutError, ConnectionResetError, BrokenPipeError, OSError):
4084
+ # Common transient socket errors: client opened conn and walked
4085
+ # away (recv timeout), client killed mid-write, etc. Drop this
4086
+ # connection but keep the server thread running.
4087
+ pass
4088
+ except Exception:
4089
+ # Catch-all so a single bad request never takes down the IPC
4090
+ # server thread (which would silently break /bg start's promise).
4091
+ pass
4055
4092
  finally:
4056
4093
  try:
4057
4094
  conn.close()
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dulus"
7
- version = "0.2.26"
7
+ version = "0.2.28"
8
8
  description = "Spanish-first multi-provider AI CLI — 14 NVIDIA models free, Mesa Redonda, voice, TTS, RTK token reducer, MemPalace"
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
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