dulus 0.2.33__tar.gz → 0.2.35__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.
- {dulus-0.2.33/dulus.egg-info → dulus-0.2.35}/PKG-INFO +1 -1
- {dulus-0.2.33 → dulus-0.2.35/dulus.egg-info}/PKG-INFO +1 -1
- {dulus-0.2.33 → dulus-0.2.35}/dulus.py +92 -9
- {dulus-0.2.33 → dulus-0.2.35}/gui/session_utils.py +7 -0
- {dulus-0.2.33 → dulus-0.2.35}/pyproject.toml +1 -1
- {dulus-0.2.33 → dulus-0.2.35}/tools.py +1 -1
- {dulus-0.2.33 → dulus-0.2.35}/LICENSE +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/MANIFEST.in +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/README.md +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/agent.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/backend/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/backend/compressor.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/backend/context.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/backend/githook.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/backend/marketplace.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/backend/mempalace_bridge.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/backend/personas.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/backend/plugins.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/backend/server.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/backend/tasks.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/batch_api.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/checkpoint/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/checkpoint/hooks.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/checkpoint/store.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/checkpoint/types.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/claude_code_watcher.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/clipboard_utils.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/cloudsave.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/common.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/compaction.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/config.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/context.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/data/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/data/active_persona.json +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/data/context.json +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/data/marketplace.json +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/data/personas.json +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/data/plugins/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/data/plugins/composio/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/data/plugins/composio/composio_plugin/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/data/plugins/composio/plugin.json +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/data/plugins/composio/plugin_tool.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/data/tasks.json +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/README.md +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/api.html +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/architecture.md +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/azure-speech-template.json +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/dashboard/index.html +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/divider.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/generate.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/hero.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/index.html +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/news.md +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/nvidia-models.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/particle-playground.html +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/personas/index.html +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/poetry-banner.png +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/preview.html +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/sec-agents.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/sec-brainstorm.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/sec-bridges.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/sec-features.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/sec-freetier.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/sec-memory.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/sec-models.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/sec-perms.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/sec-plugins.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/sec-quickstart.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/sec-ssj.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/spinners.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/split-pane.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/terminal-boot.svg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/docs/uploads/particle-playground.html +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/dulus.egg-info/SOURCES.txt +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/dulus.egg-info/requires.txt +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/dulus.egg-info/top_level.txt +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/dulus_gui.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/dulus_mcp/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/dulus_mcp/client.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/dulus_mcp/config.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/dulus_mcp/tools.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/dulus_mcp/types.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/gui/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/gui/agent_bridge.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/gui/chat_widget.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/gui/main_window.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/gui/personas.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/gui/settings_dialog.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/gui/sidebar.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/gui/tasks_view.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/gui/themes.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/gui/tool_panel.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/input.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/license_manager.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/memory/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/memory/audit.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/memory/consolidator.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/memory/context.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/memory/offload.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/memory/palace.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/memory/scan.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/memory/sessions.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/memory/store.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/memory/tools.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/memory/types.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/memory/vector_search.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/multi_agent/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/multi_agent/subagent.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/multi_agent/tools.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/offload_helper.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/plugin/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/plugin/autoadapter.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/plugin/loader.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/plugin/recommend.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/plugin/store.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/plugin/types.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/providers.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/setup.cfg +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/skill/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/skill/builtin.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/skill/clawhub.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/skill/executor.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/skill/loader.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/skill/tools.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/skills.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/spinner.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/string_utils.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/subagent.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/task/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/task/store.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/task/tools.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/task/types.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tests/test_checkpoint.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tests/test_compaction.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tests/test_diff_view.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tests/test_injection_fix.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tests/test_license.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tests/test_mcp.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tests/test_memory.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tests/test_plugin.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tests/test_skills.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tests/test_subagent.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tests/test_task.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tests/test_telegram_buffer.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tests/test_tool_registry.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tests/test_voice.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tmux_offloader.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tmux_tools.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/tool_registry.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/ui/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/ui/input.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/ui/render.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/voice/__init__.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/voice/keyterms.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/voice/recorder.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/voice/stt.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/voice/tts.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/webchat.py +0 -0
- {dulus-0.2.33 → dulus-0.2.35}/webchat_server.py +0 -0
|
@@ -154,11 +154,11 @@ if str(DULUS_CODE_ROOT) not in sys.path:
|
|
|
154
154
|
from tools import ask_input_interactive, _tg_thread_local, _is_in_tg_turn
|
|
155
155
|
import input as dulus_input
|
|
156
156
|
try:
|
|
157
|
-
import paste_placeholders as _paste_ph
|
|
157
|
+
import paste_placeholders as _paste_ph # type: ignore
|
|
158
158
|
except ImportError:
|
|
159
159
|
_paste_ph = None # type: ignore[assignment]
|
|
160
160
|
try:
|
|
161
|
-
import git_prompt as _git_prompt
|
|
161
|
+
import git_prompt as _git_prompt # type: ignore
|
|
162
162
|
except ImportError:
|
|
163
163
|
_git_prompt = None # type: ignore[assignment]
|
|
164
164
|
try:
|
|
@@ -1187,7 +1187,9 @@ def save_latest(args: str, state, config=None) -> bool:
|
|
|
1187
1187
|
|
|
1188
1188
|
import uuid
|
|
1189
1189
|
now = datetime.now()
|
|
1190
|
-
sid = uuid.uuid4().hex[:8]
|
|
1190
|
+
sid = cfg.get("_session_id") or uuid.uuid4().hex[:8]
|
|
1191
|
+
# Ensure config has it for consistency
|
|
1192
|
+
cfg["_session_id"] = sid
|
|
1191
1193
|
ts = now.strftime("%H%M%S")
|
|
1192
1194
|
date_str = now.strftime("%Y-%m-%d")
|
|
1193
1195
|
data = _build_session_data(state, session_id=sid)
|
|
@@ -1201,6 +1203,14 @@ def save_latest(args: str, state, config=None) -> bool:
|
|
|
1201
1203
|
# 2. daily/YYYY-MM-DD/session_HHMMSS_sid.json
|
|
1202
1204
|
day_dir = DAILY_DIR / date_str
|
|
1203
1205
|
day_dir.mkdir(parents=True, exist_ok=True)
|
|
1206
|
+
|
|
1207
|
+
# Delete older copies of this same session ID to prevent duplication
|
|
1208
|
+
for old_copy in day_dir.glob(f"session_*_{sid}.json"):
|
|
1209
|
+
try:
|
|
1210
|
+
old_copy.unlink()
|
|
1211
|
+
except Exception:
|
|
1212
|
+
pass
|
|
1213
|
+
|
|
1204
1214
|
daily_path = day_dir / f"session_{ts}_{sid}.json"
|
|
1205
1215
|
daily_path.write_text(payload)
|
|
1206
1216
|
|
|
@@ -3856,7 +3866,7 @@ def cmd_exit(_args: str, _state, config) -> bool:
|
|
|
3856
3866
|
session_data = _build_session_data(_state)
|
|
3857
3867
|
gist_id, err_msg = upload_session(
|
|
3858
3868
|
session_data, config["gist_token"],
|
|
3859
|
-
|
|
3869
|
+
gist_id=config.get("cloudsave_last_gist_id"),
|
|
3860
3870
|
)
|
|
3861
3871
|
if err_msg:
|
|
3862
3872
|
err(f"Cloud sync failed: {err_msg}")
|
|
@@ -5932,19 +5942,85 @@ def _run_daemon(config: dict) -> None:
|
|
|
5932
5942
|
# `is_background` kwarg, which made every Telegram/IPC turn raise
|
|
5933
5943
|
# silently and never actually answer the user. Fixed now.
|
|
5934
5944
|
def _daemon_run_query(msg):
|
|
5945
|
+
qlock = config.get("_query_lock")
|
|
5946
|
+
if qlock:
|
|
5947
|
+
qlock.acquire()
|
|
5935
5948
|
try:
|
|
5949
|
+
import sys
|
|
5950
|
+
import checkpoint as ckpt
|
|
5936
5951
|
from agent import run as agent_run
|
|
5937
5952
|
from context import build_system_prompt
|
|
5953
|
+
from dulus import save_latest, _tg_get_chat_ids, _telegram_stop, _tg_send
|
|
5954
|
+
|
|
5938
5955
|
sys_prompt = build_system_prompt(config)
|
|
5939
|
-
|
|
5940
|
-
#
|
|
5956
|
+
is_telegram_turn = config.get("_telegram_incoming", False)
|
|
5957
|
+
# Basic heuristic: if the message starts with System Automated Event, it's a background event
|
|
5958
|
+
is_background = msg.startswith("(System Automated Event)")
|
|
5959
|
+
|
|
5960
|
+
if is_background and not is_telegram_turn:
|
|
5961
|
+
ttok = config.get("telegram_token")
|
|
5962
|
+
_tids = _tg_get_chat_ids(config)
|
|
5963
|
+
tchat = config.get("_active_tg_chat_id") or (_tids[0] if _tids else 0)
|
|
5964
|
+
if ttok and tchat and _telegram_stop and not _telegram_stop.is_set():
|
|
5965
|
+
import threading as _tg_thread
|
|
5966
|
+
from dulus import _tg_send
|
|
5967
|
+
_tg_thread.Thread(target=_tg_send, args=(ttok, tchat, f"⚙ {msg}"), daemon=True).start()
|
|
5968
|
+
|
|
5941
5969
|
for ev in agent_run(msg, state, config, sys_prompt):
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5970
|
+
if "webchat_server" in sys.modules and sys.modules["webchat_server"].is_running():
|
|
5971
|
+
try:
|
|
5972
|
+
import webchat_server as _wcs
|
|
5973
|
+
r = _wcs._event_to_dict(ev)
|
|
5974
|
+
if r:
|
|
5975
|
+
if isinstance(r, tuple):
|
|
5976
|
+
payload, wait_event = r
|
|
5977
|
+
_wcs.broadcast_event("chunk", payload)
|
|
5978
|
+
wait_event.wait(timeout=2.0)
|
|
5979
|
+
else:
|
|
5980
|
+
_wcs.broadcast_event("chunk", r)
|
|
5981
|
+
except Exception:
|
|
5982
|
+
pass
|
|
5945
5983
|
_ = ev
|
|
5984
|
+
|
|
5985
|
+
try:
|
|
5986
|
+
tracked = ckpt.get_tracked_edits()
|
|
5987
|
+
last_snaps = ckpt.list_snapshots(session_id)
|
|
5988
|
+
skip = False
|
|
5989
|
+
if not tracked and last_snaps:
|
|
5990
|
+
if len(state.messages) == last_snaps[-1].get("message_index", -1):
|
|
5991
|
+
skip = True
|
|
5992
|
+
if not skip:
|
|
5993
|
+
ckpt.make_snapshot(session_id, state, config, msg, tracked_edits=tracked)
|
|
5994
|
+
ckpt.reset_tracked()
|
|
5995
|
+
except Exception:
|
|
5996
|
+
pass
|
|
5997
|
+
|
|
5998
|
+
try:
|
|
5999
|
+
save_latest("", state, config)
|
|
6000
|
+
except Exception:
|
|
6001
|
+
pass
|
|
6002
|
+
|
|
6003
|
+
# Broadcast background notifications to Telegram to maintain parity with REPL
|
|
6004
|
+
if is_background and not is_telegram_turn:
|
|
6005
|
+
ttok = config.get("telegram_token")
|
|
6006
|
+
_tids = _tg_get_chat_ids(config)
|
|
6007
|
+
tchat = config.get("_active_tg_chat_id") or (_tids[0] if _tids else 0)
|
|
6008
|
+
|
|
6009
|
+
if ttok and tchat and _telegram_stop and not _telegram_stop.is_set():
|
|
6010
|
+
if state.messages and state.messages[-1].get("role") == "assistant":
|
|
6011
|
+
ans_content = state.messages[-1].get("content", "")
|
|
6012
|
+
if isinstance(ans_content, list):
|
|
6013
|
+
parts = [b["text"] if isinstance(b, dict) else str(b) for b in ans_content if (isinstance(b, dict) and b.get("type") == "text") or isinstance(b, str)]
|
|
6014
|
+
ans_content = "\n".join(parts)
|
|
6015
|
+
if ans_content:
|
|
6016
|
+
import threading as _tg_thread
|
|
6017
|
+
_tg_thread.Thread(target=_tg_send, args=(ttok, tchat, ans_content), daemon=True).start()
|
|
6018
|
+
|
|
5946
6019
|
except Exception as _e:
|
|
5947
6020
|
err(f"daemon run_query error: {type(_e).__name__}: {_e}")
|
|
6021
|
+
finally:
|
|
6022
|
+
if qlock:
|
|
6023
|
+
qlock.release()
|
|
5948
6024
|
config["_run_query_callback"] = _daemon_run_query
|
|
5949
6025
|
|
|
5950
6026
|
# Register slash-command callback so Telegram and WebChat can run
|
|
@@ -6016,6 +6092,12 @@ def _run_daemon(config: dict) -> None:
|
|
|
6016
6092
|
config["_ipc_thread"] = ti
|
|
6017
6093
|
ti.start()
|
|
6018
6094
|
|
|
6095
|
+
# Job Sentinel: Detect background completions and wake up the agent
|
|
6096
|
+
if config.get("_job_sentinel_thread") is None:
|
|
6097
|
+
tj = threading.Thread(target=_job_sentinel_loop, args=(config, state), daemon=True)
|
|
6098
|
+
config["_job_sentinel_thread"] = tj
|
|
6099
|
+
tj.start()
|
|
6100
|
+
|
|
6019
6101
|
# 'accent' / 'orange' are only present in some custom themes; default
|
|
6020
6102
|
# palette is {blue, cyan, gray, green, magenta, red, white, yellow}.
|
|
6021
6103
|
# KeyError here would crash the daemon before the user ever sees a prompt.
|
|
@@ -6413,6 +6495,7 @@ def cmd_voice(args: str, state, config) -> bool:
|
|
|
6413
6495
|
name = next(d["name"] for d in devices if d["index"] == idx)
|
|
6414
6496
|
ok(f"Microphone set to: [{idx}] {name}")
|
|
6415
6497
|
try:
|
|
6498
|
+
from config import save_config
|
|
6416
6499
|
save_config(config)
|
|
6417
6500
|
except Exception:
|
|
6418
6501
|
pass
|
|
@@ -107,6 +107,13 @@ def save_session(state, config: dict, session_id: str | None = None) -> str:
|
|
|
107
107
|
# 3. Save to daily folder
|
|
108
108
|
day_dir = DAILY_DIR / date_str
|
|
109
109
|
day_dir.mkdir(parents=True, exist_ok=True)
|
|
110
|
+
|
|
111
|
+
# Prune old copies for this session ID
|
|
112
|
+
for old_copy in day_dir.glob(f"session_*_{sid}.json"):
|
|
113
|
+
try:
|
|
114
|
+
old_copy.unlink()
|
|
115
|
+
except: pass
|
|
116
|
+
|
|
110
117
|
daily_path = day_dir / f"session_{ts}_{sid}.json"
|
|
111
118
|
daily_path.write_text(payload, encoding="utf-8")
|
|
112
119
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dulus"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.35"
|
|
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"
|
|
@@ -1893,7 +1893,7 @@ def _sleeptimer(seconds: int, config: dict) -> str:
|
|
|
1893
1893
|
|
|
1894
1894
|
t = threading.Thread(target=worker, daemon=True)
|
|
1895
1895
|
t.start()
|
|
1896
|
-
return f"Timer successfully scheduled for {seconds} seconds.
|
|
1896
|
+
return f"Timer successfully scheduled for {seconds} seconds. Do NOT output anything. End your turn silently and wait for the system to wake you up."
|
|
1897
1897
|
|
|
1898
1898
|
|
|
1899
1899
|
def _print_to_console(content: str = "", style: str = "normal", prefix: str = "", from_line: int = None, to_line: int = None, file_path: str = None, config: dict = None) -> str:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|