## Dependency Graph

▶_create_coordination_tasks.py 66 LOC
Create Mesa Redonda coordination tasks.
Imports
sys task
▶_tmp_check_tasks.py 15 LOC
Imports
json sys
▶_update_legacy_tasks.py 32 LOC
Update legacy Mesa Redonda tasks (13-21) with owners and phases.
Imports
sys task
▶agent.py 350 LOC
Core agent loop: neutral message format, multi-provider streaming.
Classes (5)
class AgentState
Mutable session state. messages use the neutral provider-independent format.
class ToolStart
class ToolEnd
class TurnDone
class PermissionRequest
Functions (4)
def _interruptible_stream(gen)
Run a generator in a daemon thread, yield events via Queue. Ctrl+C (KeyboardInterrupt) is always deliverable because the main thread only blocks on queue.get(timeout=0.1) — never on a raw socket.
def run(user_message: str, state: AgentState, config: dict, system_prompt: str, depth: int = 0, cancel_check = None)
Multi-turn agent loop (generator). Yields: TextChunk | ThinkingChunk | ToolStart | ToolEnd | PermissionRequest | TurnDone Args: depth: sub-agent nesting depth, 0 for top-level cancel_check: callable returning True to abort the loop early
def _check_permission(tc: dict, config: dict)
Return True if operation is auto-approved (no need to ask user).
def _permission_desc(tc: dict)
Imports
__future__compaction dataclasses os pathlib providers queue threading time tool_registry tools typing uuid
▶auto_context_loader.py 85 LOC
Auto Context Loader for Dulus Automatically loads relevant context from MemPalace at the start of each conversation
Functions (2)
def load_initial_context()
Load initial context from MemPalace for the conversation.
def format_context_for_display(context_result: Dict[str, Any])
Format the context data for display to the user.
Imports
json os sys typing
▶batch_api.py 307 LOC
Dulus Batch API — provider-agnostic OpenAI-compatible batch processing. Works with any provider that supports the OpenAI Batch API format: - OpenAI (api.openai.com) - Kimi/Moonshot (api.moonshot.ai) - Any OpenAI-compatible endpoint Usage: mgr = BatchManager(api_key="sk-...", base_url="https://api.openai.com") jsonl = mgr.prepare_jsonl(["prompt1", "prompt2"], model="gpt-4o-mini") file_id = mgr.upload_file(jsonl) batch_id = mgr.create_batch(file_id)
Classes (1)
class BatchManager
Provider-agnostic manager for the OpenAI-compatible Batch API.
↳ __init__(self, api_key: str, base_url: str = OPENAI_BASE_URL)
↳ _headers(self, content_type: str = 'application/json')
↳ prepare_jsonl(self, prompts: List[str], model: str = 'gpt-4o-mini', system_prompt: str = None, endpoint: str = '/v1/chat/completions')
Convert a list of prompts into JSONL content for the Batch API. Args: prompts: List of user prompts. model: Model name (provider-specific). system_prompt: Defaults to BATCH_
↳ upload_file(self, jsonl_content: str, filename: str = 'batch_input.jsonl')
Upload JSONL content and return the file_id.
↳ create_batch(self, file_id: str, endpoint: str = '/v1/chat/completions', completion_window: str = '24h')
Create a batch from an uploaded file. Returns batch_id.
↳ retrieve_batch(self, batch_id: str)
Get batch status/info.
↳ cancel_batch(self, batch_id: str)
Cancel a running batch.
↳ get_file_content(self, file_id: str)
Download file content (e.g. batch results).
Functions (4)
def save_batch_job(batch_id: str, description: str = '', file_id: str = '', provider: str = 'unknown')
Save a batch job record locally in ~/.dulus/jobs/.
def list_batch_jobs(include_pollers: bool = True, **_kw)
List saved batch jobs from ~/.dulus/jobs/.
def update_batch_job_status(batch_id: str, status_info: Dict[str, Any])
Update a batch job's status in its local file.
def get_batch_job_by_id(batch_id: str)
Get a batch job by ID (checks both batch and poller files).
Imports
json os time typing urllib.request
▶checkpoint/__init__.py 27 LOC
Checkpoint system: automatic file snapshots with rewind support.
Imports
hooks store types
▶checkpoint/hooks.py 90 LOC
Checkpoint hooks: intercept Write/Edit/NotebookEdit to back up files before modification. Import this module after tools are registered to install the hooks.
Functions (5)
def set_session(session_id: str)
def get_tracked_edits()
Return the current interval's tracked edits (for make_snapshot).
def reset_tracked()
Clear tracked edits after a snapshot is created.
def _backup_before_write(file_path: str)
Back up a file before it is modified (first-write-wins per snapshot interval).
def install_hooks()
Wrap Write/Edit/NotebookEdit tool functions to call backup before execution.
Imports
__future__pathlib
▶checkpoint/store.py 314 LOC
Checkpoint store: file-level backup + snapshot persistence. Directory layout: ~/.dulus/checkpoints/<session_id>/ snapshots.json # list of Snapshot metadata backups/ <hash>@v<N> # actual file copies
Functions (17)
def _checkpoints_root()
def _session_dir(session_id: str)
def _backups_dir(session_id: str)
def _snapshots_file(session_id: str)
def _path_hash(file_path: str)
Deterministic short hash from file path (not content).
def _next_version(file_path: str)
def _load_snapshots(session_id: str)
def _save_snapshots(session_id: str, snapshots: list[Snapshot])
def track_file_edit(session_id: str, file_path: str)
Back up a file before it is edited (first-write-wins per snapshot interval). Returns the backup filename, or None if the file doesn't exist yet.
def make_snapshot(session_id: str, state: Any, config: dict, user_prompt: str, tracked_edits: dict[str, str | None] | None = None)
Create a snapshot after a user prompt has been processed. tracked_edits: dict mapping file_path → backup_filename (or None if new file). Populated by hooks.py during the turn.
def list_snapshots(session_id: str)
Return lightweight summaries of all snapshots.
def get_snapshot(session_id: str, snapshot_id: int)
def rewind_files(session_id: str, snapshot_id: int)
Restore files to their state at the given snapshot. Returns list of restored/deleted file paths.
def files_changed_since(session_id: str, snapshot_id: int)
List files that have been changed in snapshots after the given one.
def delete_session_checkpoints(session_id: str)
Delete all checkpoints for a session.
def cleanup_old_sessions(max_age_days: int = 30)
Remove checkpoint sessions older than max_age_days. Returns count removed.
def reset_file_versions()
Reset per-file version counters (for testing).
Imports
__future__datetime hashlib json os pathlib shutil time types typing
▶checkpoint/types.py 80 LOC
Checkpoint system types: FileBackup and Snapshot dataclasses.
Classes (2)
class FileBackup
A single file's backup reference within a snapshot. backup_filename: hash@vN name in the backups/ dir, or None if the file did not exist before (meaning restore = delete). version: monotonically increasing per-file version counter. backup_time: ISO timestamp of when the backup was
↳ to_dict(self)
↳ from_dict(cls, data: dict)
class Snapshot
A checkpoint snapshot — metadata about conversation + file state.
↳ to_dict(self)
↳ from_dict(cls, data: dict)
Imports
__future__dataclasses typing
▶claude_code_watcher.py 214 LOC
claude_code_watcher.py Watches a Claude Code session JSONL file and extracts assistant responses in real time. Can print to stdout or POST to a Dulus/webhook endpoint. v2: Groups multi-part assistant turns (text + tool_use + text) into one complete message before sending. Fixes the bug where text after a tool call was sent as a separate/missing message. Usage: python claude_code_watcher.py python claude_code_watcher.py --session <path_to.jsonl> python claude_code_wa
Functions (7)
def find_latest_session()
Find the most recently modified JSONL session file.
def extract_text_blocks(entry: dict)
Return all text strings from an assistant entry's content blocks.
def has_tool_use(entry: dict)
True if this entry contains a tool_use block (mid-turn, more may follow).
def is_assistant(entry: dict)
def post_message(text: str, post_url: str)
def watch(session_path: Path, post_url: str | None = None, poll_interval: float = 0.5)
Tail the JSONL file and emit complete assistant turns.
def main()
Imports
argparse json os pathlib sys time
▶cloudsave.py 159 LOC
Cloud sync for dulus sessions via GitHub Gist. Supported provider: GitHub Gist - No extra cloud account needed beyond a GitHub Personal Access Token - Sessions stored as private Gists (JSON), browsable in GitHub UI - Zero extra dependencies (uses urllib from stdlib) Config keys (stored in ~/.dulus/config.json): gist_token — GitHub Personal Access Token (needs 'gist' scope) cloudsave_auto — bool: auto-upload on /exit cloudsave_last_gist_id — last uploaded gist ID (for in-pla
Functions (6)
def _request(method: str, path: str, token: str, body: dict | None = None)
def _request_safe(method: str, path: str, token: str, body: dict | None = None)
Like _request but returns (result, error_str).
def validate_token(token: str)
Check token is valid and has gist scope. Returns (ok, message).
def upload_session(session_data: dict, token: str, description: str = '', gist_id: str | None = None)
Create or update a Gist with the session JSON. Returns (gist_id, error). On success gist_id is the Gist ID.
def list_sessions(token: str, max_results: int = 20)
List Gists tagged as dulus sessions. Returns (list of {id, description, updated_at, url}), error).
def download_session(token: str, gist_id: str)
Fetch a Gist and return the parsed session JSON. Returns (session_data, error).
Imports
__future__datetime json urllib.error urllib.request
▶common.py 173 LOC
Functions (11)
def _rgb(hex_str: str)
Convert '#rrggbb' → ANSI 24-bit foreground escape.
def apply_theme(name: str)
Mutate the global ANSI color map in-place to a named theme.
def clr(text: str, *keys)
def info(msg: str)
def ok(msg: str)
def warn(msg: str)
def err(msg: str)
def stream_thinking(chunk: str, verbose: bool)
def print_tool_start(name: str, inputs: dict)
def print_tool_end(name: str, result: str, success: bool = True, verbose: bool = False, auto_show: bool = True)
def sanitize_text(text: str)
Remove invalid UTF-16 surrogates and ensure valid UTF-8. On Windows consoles (cp1252) pasted emojis often become stray surrogates (e.g. \ud83d\udcec) which later explode with: 'utf-8' codec can't encode characters: surrogates not allowed This helper cleans them *once* at the boundary before the
Imports
json sys
▶compaction.py 355 LOC
Context window management: two-layer compression for long conversations.
Functions (9)
def estimate_tokens(messages: list, model: str = '', config: dict | None = None)
Estimate token count. For Kimi/Moonshot models, uses the native Kimi API token estimation endpoint if API key is available. Otherwise falls back to character-based estimation. Args: messages: list of message dicts with "content" field (str or list of dicts) model: model string (optional, e
def get_context_limit(model: str)
Look up context window size for a model. Args: model: model string (e.g. "claude-opus-4-6", "ollama/llama3.3") Returns: context limit in tokens
def snip_old_tool_results(messages: list, max_chars: int = 2000, preserve_last_n_turns: int = 6)
Truncate tool-role messages older than preserve_last_n_turns from end. For old tool messages whose content exceeds max_chars, keep the first half and last quarter, inserting '[... N chars snipped ...]' in between. Mutates in place and returns the same list. Args: messages: list of message dict
def _score_message_priority(message: dict)
Score a message by importance (higher = more important to preserve). Returns an integer priority score. Messages with score >= 3 are considered 'high priority' and should be preserved during compaction.
def find_split_point(messages: list, keep_ratio: float = 0.3, model: str = '', config: dict | None = None)
Find index that splits messages so ~keep_ratio of tokens are in the recent portion. Walks backwards from end, accumulating token estimates, and returns the index where the recent portion reaches ~keep_ratio of total tokens. Args: messages: list of message dicts keep_ratio: fraction of toke
def compact_messages(messages: list, config: dict, focus: str = '')
Compress old messages into a summary via LLM call. Splits at find_split_point, summarizes old portion, returns [summary_msg, ack_msg, *recent_messages]. Smart behavior: messages with high priority score (errors, decisions, file references) are preserved verbatim instead of being summarized away.
def maybe_compact(state, config: dict)
Check if context window is getting full and compress if needed. Runs snip_old_tool_results first, then auto-compact if still over threshold. Args: state: AgentState with .messages list config: agent config dict (must contain "model") Returns: True if compaction was performed
def _restore_plan_context(config: dict)
If in plan mode, return messages that restore plan file context.
def manual_compact(state, config: dict, focus: str = '')
User-triggered compaction via /compact. Not gated by threshold. Returns (success, info_message).
Imports
__future__providers
▶config.py 166 LOC
Configuration management for Dulus (multi-provider).
Functions (9)
def _encrypt(value: str)
Encrypt a string with XOR + base64.
def _decrypt(value: str)
Decrypt a string encrypted with _encrypt.
def _secure_keys(cfg: dict)
Encrypt all *_api_key values before saving.
def _unsecure_keys(cfg: dict)
Decrypt all *_api_key values after loading.
def load_config()
def save_config(cfg: dict)
def current_provider(cfg: dict)
def has_api_key(cfg: dict)
Check whether the active provider has an API key configured.
def calc_cost(model: str, in_tokens: int, out_tokens: int)
Imports
json os pathlib
▶context.py 177 LOC
System context: DULUS.md, git info, cwd injection.
Functions (8)
def get_git_info(config: dict | None = None)
def get_dulus_md()
def get_project_memory_index()
Auto-load project-scope memories from .dulus-context/memory/MEMORY.md. Looks in cwd and parents (first match wins). Returns the index so the model knows what memories exist and can Read individual files on demand.
def _detect_shell_type(config: dict | None = None)
Resolve which shell family to advertise: 'bash', 'powershell', or 'cmd'.
def get_platform_hints(config: dict | None = None)
def _build_ollama_system_prompt(config: dict | None = None)
def _normalize_thinking_level(config: dict | None)
def build_system_prompt(config: dict | None = None)
Imports
datetime os pathlib subprocess
▶context_integration.py 119 LOC
Context Integration Module for Dulus Integrates MemPalace context loading into Dulus's conversation flow
Classes (1)
class ContextManager
↳ __init__(self)
↳ load_context(self)
Load context from MemPalace and store it for use during the conversation.
↳ get_context_summary(self)
Get a summary of loaded context for display.
↳ search_context(self, query: str)
Search through loaded context for relevant items.
Functions (3)
def initialize_conversation_context()
Initialize context at the start of a conversation.
def get_current_context_summary()
Get current context summary for display.
def search_current_context(query: str)
Search current context for relevant information.
Imports
json os typing
▶demos/make_brainstorm_demo.py 367 LOC
Generate animated GIF demo of cheetahclaws /brainstorm command using PIL. Simulates the full brainstorm session: agent count prompt → persona generation → multi-agent debate → synthesis.
Functions (19)
def make_font(size = FONT_SIZE, bold = False)
def seg(t, c = TEXT, b = False)
def render_line(draw, y, segments, x_start = PAD_X)
def blank_frame()
def draw_frame(lines_segments)
def prompt_line(text = '', cursor = False)
def ok_line(msg)
def info_line(msg)
def agent_thinking(icon, role)
def agent_done()
def claude_header()
def claude_sep()
def text_line(t, indent = 2)
def dim_line(t, indent = 2)
def tool_line(icon, name, arg)
def tool_ok(msg)
def build_scenes()
def _build_palette()
def render_gif(output_path)
Imports
PIL os
▶demos/make_demo.py 416 LOC
Generate animated GIF demo of cheetahclaws using PIL. Simulates a realistic terminal session with tool calls.
Functions (18)
def make_font(size = FONT_SIZE, bold = False)
def seg(t, c = TEXT, b = False)
def segs(*args)
def render_line(draw, y, segments, x_start = PAD_X)
def blank_frame()
def draw_frame(lines_segments)
lines_segments: list of either - list[Seg] → rendered as a line - None → blank line Returns PIL Image.
def prompt_line(text = '', cursor = False)
def claude_header()
def claude_sep()
def tool_line(icon, name, arg, color = CYAN)
def tool_ok(msg)
def tool_err(msg)
def text_line(t, indent = 2)
def dim_line(t, indent = 4)
def build_scenes()
Return list of (frame_content, duration_ms).
def _build_explicit_palette()
Build a 256-entry palette from our exact theme colors. Returns flat list of 768 ints (R,G,B, R,G,B, ...) suitable for putpalette().
def render_gif(output_path = 'demo.gif')
def render_screenshot(output_path = 'screenshot.png')
Single high-quality screenshot showing a complete session.
Imports
PIL os textwrap
▶demos/make_proactive_demo.py 359 LOC
Generate animated GIF demo of cheetahclaws proactive / background-event feature. Shows: timer reminder set → idle at prompt → [Background Event Triggered] → Claude fires reminder → user asks again → second reminder fires.
Functions (16)
def make_font(size = FONT_SIZE, bold = False)
def seg(t, c = TEXT, b = False)
def render_line(draw, y, segments, x_start = PAD_X)
def blank_frame()
def draw_frame(lines_segments)
def prompt_line(text = '', cursor = False)
def ok_line(msg)
def claude_header()
def claude_sep()
def text_line(t, indent = 2)
def tool_line(icon, name, arg)
def tool_ok(msg)
def bg_event_line()
def build_scenes()
def _build_palette()
def render_gif(output_path)
Imports
PIL os
▶demos/make_ssj_demo.py 491 LOC
Generate animated GIF demo of cheetahclaws SSJ Developer Mode. Shows: /ssj menu → Brainstorm → TODO viewer → Worker → Exit
Functions (18)
def make_font(size = FONT_SIZE, bold = False)
def seg(t, c = TEXT, b = False)
def render_line(draw, y, segments, x_start = PAD_X)
def draw_frame(lines_segments)
def prompt_line(text = '', cursor = False)
def ssj_prompt(text = '', cursor = False)
def claude_header()
def claude_sep()
def tool_line(icon, name, arg, color = CYAN)
def tool_ok(msg)
def text_line(t, indent = 2)
def dim_line(t, indent = 4)
def ok_line(t)
def info_line(t)
def err_line(t)
def build_scenes()
def _build_palette()
def render_gif(output_path = 'ssj_demo.gif')
Imports
PIL os
▶demos/make_telegram_demo.py 531 LOC
Generate animated GIF demo of cheetahclaws Telegram Bridge. Shows: setup → auto-start → incoming messages → tool calls → response → stop
Functions (20)
def make_font(size = FONT_SIZE, bold = False)
def seg(t, c = TEXT, b = False)
def render_line(draw, y, segments, x_start = PAD_X)
def draw_phone(img, chat_messages)
Draw a minimal phone-style Telegram chat panel on the right. chat_messages: list of (sender, text, color) sender = "user" | "bot"
def draw_frame(lines_segments, chat_messages = None)
def prompt_line(text = '', cursor = False)
def ok_line(t)
def info_line(t)
def warn_line(t)
def dim_line(t, indent = 4)
def claude_header()
def claude_sep()
def tool_line(icon, name, arg, color = CYAN)
def tool_ok(msg)
def text_line(t, indent = 2)
def tg_incoming(text)
Telegram incoming message line shown in terminal.
def tg_sent(preview)
def build_scenes()
def _build_palette()
def render_gif(output_path = 'telegram_demo.gif')
Imports
PIL os
▶dulus.py 7781 LOC
Dulus — Next-gen Python Autonomous Agent. Usage: python dulus.py [options] [prompt] dulus [options] [prompt] (if dulus.bat is in PATH) Options: -p, --print Non-interactive: run prompt and exit (also --print-output) -m, --model MODEL Override model (e.g., -m kimi/kimi-k2.5, -m gpt-4o) --accept-all Never ask permission (dangerous) --verbose Show thinking + token counts --version Print version and exit -h, --help Sh
Functions (111)
def _rl_safe(prompt: str)
Wrap ANSI escape sequences with \001/\002 so readline ignores them when calculating visible prompt width. Fixes duplicate-on-scroll and cursor-misalignment bugs in terminals that use readline.
def render_diff(text: str)
Print diff text with ANSI colors: red for removals, green for additions.
def _has_diff(text: str)
Check if text contains a unified diff.
def _make_renderable(text: str)
Return a Rich renderable: Markdown if text contains markup, else plain.
def _start_live()
Start a Rich Live block for in-place Markdown streaming (no-op if not Rich).
def stream_text(chunk: str)
Buffer chunk; update Live in-place when Rich available, else print directly. Safety: if accumulated text exceeds _LIVE_LINE_LIMIT lines, auto-switch from Rich Live to plain streaming to prevent terminal re-render duplication on terminals that can't handle large Live areas (Windows Terminal, etc.).
def flush_response()
Commit buffered text to screen: stop Live (freezes rendered Markdown in place).
def _run_tool_spinner()
Background spinner on a single line using carriage return. In split-input mode stdout is redirected to _OutputRedirector (which line-buffers and strips \r), so each spinner frame would eventually accumulate into the output area. Skip writes in that case — the split layout has its own visual afforda
def _start_tool_spinner(phrase: str | None = None)
def _change_spinner_phrase()
Change the spinner phrase without stopping it.
def _stop_tool_spinner()
def print_tool_start(name: str, inputs: dict, verbose: bool)
Show tool invocation.
def print_tool_end(name: str, result: str, verbose: bool, config: dict = None)
def _tool_desc(name: str, inputs: dict)
def ask_permission_interactive(desc: str, config: dict)
def _proactive_watcher_loop(config)
Background daemon that fires a wake-up prompt after a period of inactivity.
def cmd_help(_args: str, _state, config)
def cmd_model(args: str, _state, config)
def _generate_personas(topic: str, curr_model: str, config: dict, count: int = 5)
Ask the LLM to generate `count` topic-appropriate expert personas as a dict.
def _interactive_ollama_picker(config: dict)
Prompt the user to select from locally available Ollama models.
def cmd_brainstorm(args: str, state, config)
Run a multi-persona iterative brainstorming session on the project. Usage: /brainstorm [topic]
def _save_synthesis(state, out_file: str)
Append the last assistant response as the synthesis section of the brainstorm file.
def _print_dulus_banner(config: dict, with_logo: bool = True)
Reprint the Dulus logo + info box (used by startup and /clear).
def cmd_clear(_args: str, state, config)
def _redact_secret(value)
Mask all but last 4 chars of a secret value.
def _is_secret_key(key: str)
def cmd_config(args: str, _state, config)
def _atomic_write_json(path: Path, data)
Write JSON atomically: write to .tmp sibling, then rename. Prevents half-written files when the process is killed mid-save.
def _save_roundtable_session(log: list, save_path = None)
Save the full roundtable session log to a JSON file. Sessions go under config.MR_SESSION_DIR (~/.dulus/sessions/mr_sessions/), consistent with /save and other session artifacts. Pass an explicit save_path to override (used to keep all turns of one debate in one file).
def cmd_save(args: str, state, config)
def save_latest(args: str, state, config = None)
Save session on exit: session_latest.json + daily/ copy + append to history.json.
def cmd_load(args: str, state, config)
def cmd_resume(args: str, state, config)
def cmd_history(_args: str, state, config)
def cmd_context(_args: str, state, config)
def cmd_cost(_args: str, state, config)
def cmd_verbose(_args: str, _state, config)
def cmd_brave(_args: str, _state, config)
def cmd_git(_args: str, _state, config)
def cmd_daemon(args: str, _state, config)
def cmd_webchat(args: str, state, config)
Start the in-process webchat mirror. /webchat stop kills it.
def cmd_gui(_args: str, _state, config)
Launch the desktop GUI from the REPL.
def cmd_max_fix(args: str, _state, config)
def cmd_thinking(_args: str, _state, config)
Set or toggle extended thinking. /thinking — toggle between OFF and the last non-zero level (default 2) /thinking 0|off — disable thinking entirely /thinking 1|min — minimal: low budget + "think briefly" prompt hint /thinking 2|med|medium — mod
def _normalize_thinking_level(value)
Coerce legacy bool/int/str thinking config into an int 0-4.
def cmd_soul(args: str, state, config)
List available souls or switch the active one mid-session. /soul — list souls + show active /soul <name> — switch to <name> (e.g. chill, forensic) by injecting it as an assistant message (same mechanism as startup load)
def cmd_schema(args: str, _state, _config)
Inspect tool schemas (human-facing; model doesn't see this command). /schema — list all registered tools, grouped /schema <tool> — show full input_schema + description for one tool /schema --json <t> — raw JSON dump of the tool's schema Useful for telling the agent
def cmd_deep_override(_args: str, _state, config)
def cmd_deep_tools(_args: str, _state, config)
def cmd_autojob(_args: str, _state, config)
def cmd_auto_show(_args: str, _state, config)
def cmd_schema_autoload(_args: str, _state, config)
Toggle auto-injection of the full tool schema inventory at startup. ON → at boot, the agent sees a system message listing every registered tool (name + description, grouped). Helps the model pick the right tool instead of reinventing via Bash. Costs ~3-5k chars per session. OFF → no in
def cmd_mem_palace(args: str, _state, config)
Toggle MemPalace per-turn memory injection. /mem_palace → toggle the injection ON/OFF /mem_palace print → toggle visibility: print to console what's being injected to the model (debug — see klk pasa) ON → before each user turn, runs `search_memory(query=user_msg
def cmd_harvest(_args: str, _state, config)
Harvest fresh cookies from claude.ai using Playwright. Opens a visible Chrome window with a persistent profile. If already logged in, cookies are collected automatically. If not, log in manually then press ENTER in the terminal. Cookies are saved to ~/.dulus/claude_cookies.json and any active clau
def cmd_harvest_kimi(_args: str, _state, config)
Harvest fresh gRPC tokens from kimi.com (Consumer) using Playwright. Opens a visible Chrome window and navigates to kimi.com. You must send a single message in the browser chat for the script to intercept the necessary gRPC-Web (Connect) headers and payloads. Data is saved to ~/.dulus/kimi_consume
def cmd_harvest_gemini(_args: str, _state, config)
Harvest fresh session data from gemini.google.com using Playwright. Opens a visible Chrome window and navigates to gemini.google.com. You must send a single message in the browser chat for the script to intercept the necessary internal API headers/cookies. Data is saved to ~/.dulus/gemini_web.json
def cmd_harvest_deepseek(_args: str, _state, config)
Harvest fresh session data from chat.deepseek.com using Playwright. Opens a visible Chrome window and navigates to chat.deepseek.com. The script intercepts the Authorization Bearer token and cookies automatically on the first chat response. Data is saved to ~/.dulus/deepseek_web.json for use by de
def cmd_gemini_chats(args: str, _state, config)
Manage Gemini Web conversations. /gemini_chats — show current conversation IDs /gemini_chats new — start a fresh conversation
def cmd_kimi_chats(args: str, _state, config)
List recent Kimi.com conversations (PLACEHOLDER).
def cmd_claude_chats(args: str, _state, config)
List and select Claude.ai conversations. /claude_chats — show last 20 conversations (numbered) /claude_chats all — show all conversations /claude_chats use <N> — switch to conversation #N from the list /claude_chats use <uuid> — switch to conversation by UUID prefix
def cmd_hide_sender(_args: str, _state, config)
Toggle echoing your typed message above the sticky input bar. ON → message disappears on send; output area shows only Dulus's responses (use /history to recall what you typed). OFF → your message stays visible above as `» <msg>`.
def cmd_history(args: str, state, _config)
Show previous user messages from this session. /history → last 20 user messages /history N → last N user messages /history all → all user messages
def cmd_sticky_input(_args: str, _state, config)
Toggle the prompt_toolkit anchored input bar. ON → input line stays pinned at the bottom; background notifications flow above it (can jitter on Windows consoles). OFF → plain input() — native terminal behavior, zero redraws. Background notifications land where they land.
def cmd_theme(args: str, _state, config)
Switch the Dulus color palette. `/theme` lists, `/theme <name>` applies.
def cmd_ultra_search(_args: str, _state, config)
def cmd_permissions(args: str, _state, config)
def cmd_cwd(args: str, _state, config)
def _build_session_data(state, session_id: str | None = None)
Serialize current conversation state to a JSON-serializable dict.
def cmd_cloudsave(args: str, state, config)
Sync sessions to GitHub Gist. /cloudsave setup <token> — configure GitHub Personal Access Token /cloudsave — upload current session to Gist /cloudsave push [desc] — same as above with optional description /cloudsave auto on|off — toggle auto-upload on /exit /cloudsav
def cmd_exit(_args: str, _state, config)
def cmd_memory(args: str, _state, config)
def cmd_agents(_args: str, _state, config)
def _print_background_notifications(state = None)
Print notifications and inject completions into state messages. Returns True if any NEW completion/failure was handled.
def _job_sentinel_loop(config, state)
Background daemon that triggers run_query as soon as a job finishes. SAFETY: Only fires if the chat has been idle for at least 10 seconds. This prevents background notifications from colliding with active conversation turns (user typing, model streaming, Telegram messages). If a job finishes during
def cmd_skills(_args: str, _state, config)
def _pager(header: str, lines: list, page_size: int = 30)
Simple terminal pager: shows page_size lines, waits for n/q.
def cmd_skill(args: str, state, config)
Browse and install skills from Anthropic marketplace or ClawHub. /skill — list installed skills + show help /skill list — list installed skills /skill list local [q] — browse/search Anthropic skills on disk /skill list clawhub [q] — search ClawHub (WIP) /s
def cmd_mcp(args: str, _state, config)
Show MCP server status, or manage servers. /mcp — list all configured servers and their tools /mcp reload — reconnect all servers and refresh tools /mcp reload <name> — reconnect a single server /mcp add <name> <command> [args...] — add a stdio server to user
def cmd_plugin(args: str, _state, config)
Manage plugins. /plugin — list installed plugins /plugin install name@url [--main-agent] — install a plugin; with --main-agent, hand off to the main agent after install /plugin uninstall name — uninstall a plugin /plugin enable name
def cmd_tasks(args: str, _state, config)
Show and manage tasks. /tasks — list all tasks /tasks create <subject> — quick-create a task /tasks done <id> — mark task completed /tasks start <id> — mark task in_progress /tasks cancel <id> — mark task cancelled /tasks delete <id>
def cmd_ssj(args: str, state, config)
SSJ Developer Mode — Interactive power menu for project workflows. Usage: /ssj
def cmd_kill_tmux(_args: str, _state, config)
Kill all tmux and psmux sessions. Usage: /kill_tmux Useful when tmux/psmux sessions are stuck or causing problems.
def cmd_worker(args: str, state, config)
Auto-implement pending tasks from a todo_list.txt file. Usage: /worker — all pending tasks, default path /worker 1,4,6 — specific task numbers, default path /worker --path /some/todo.txt — all tasks from custom path /worker --path /
def _tg_api(token: str, method: str, params: dict = None)
Call Telegram Bot API. Returns parsed JSON or None on error.
def _tg_register_commands(token: str)
Register slash commands with Telegram so the native UI suggests them as the user types '/'. Called once when the bridge starts. Telegram rules: command name must be 1-32 chars, lowercase letters/digits/ underscores; description up to 256 chars; max 100 commands per bot.
def _tg_send(token: str, chat_id: int, text: str)
Send a message to a Telegram chat, splitting if too long.
def _tg_typing_loop(token: str, chat_id: int, stop_event: threading.Event, config: dict = None)
Send 'typing...' indicator every 4 seconds until stop_event is set.
def _tg_poll_loop(token: str, chat_id: int, config: dict)
Long-polling loop that reads Telegram messages and feeds them to run_query.
def _run_daemon(config: dict)
Daemon mode — keep Dulus alive in the background for Telegram bridges. No REPL, no GUI. Just a persistent state + callback loop so external triggers (Telegram) can wake the agent at any time.
def cmd_telegram(args: str, _state, config)
Telegram bot bridge — receive and respond to messages via Telegram. Usage: /telegram <bot_token> <chat_id> — start the bridge /telegram stop — stop the bridge /telegram status — show current status First time: create a bot via @BotFat
def cmd_proactive(args: str, state, config)
Manage proactive background polling. /proactive — show current status /proactive 5m — enable, trigger after 5 min of inactivity /proactive 30s / 1h — enable with custom interval /proactive off — disable
def cmd_lite(args: str, state, config)
Toggle LITE mode - reduces system prompt from ~10K to ~500 tokens. /lite — toggle ON/OFF /lite on — force ON (minimal rules) /lite off — force OFF (full rules with all examples) LITE mode keeps only essential rules: - TmuxOffload for >5 seconds - SearchLastOutput for truncated
def cmd_tts(args: str, state, config)
TTS: toggle automatic voice output, or set language / auto-listen. /tts — toggle TTS ON/OFF /tts lang <code> — set language (es, en, fr, pt, ja…) /tts lang — show current language /tts auto — toggle auto-listen: after Dulus speaks, mic opens for
def cmd_say(args: str, state, config)
TTS: speak the provided text immediately. /say <text> — speak the given text using the best available backend
def cmd_voice(args: str, state, config)
Voice input: record → STT → auto-submit as user message. /voice — record once, transcribe, submit /voice status — show backend availability /voice lang <code> — set STT language (e.g. zh, en, ja; 'auto' to reset) /voice device — list and select input microphone
def cmd_image(args: str, state, config)
Grab image from clipboard and send to vision model with optional prompt.
def cmd_checkpoint(args: str, state, config)
List or restore checkpoints. /checkpoint — list all checkpoints /checkpoint <id> — restore to checkpoint #id /checkpoint clear — delete all checkpoints for this session
def cmd_plan(args: str, state, config)
Enter/exit plan mode or show current plan. /plan <description> — enter plan mode and start planning /plan — show current plan file contents /plan done — exit plan mode, restore permissions /plan status — show plan mode status
def cmd_compact(args: str, state, config)
Manually compact conversation history. /compact — compact with default summarization /compact <focus> — compact with focus instructions
def cmd_news(args: str, state, config)
Show the latest news from docs/news.md.
def cmd_init(args: str, state, config)
Initialize a DULUS.md file in the current directory. /init — create DULUS.md with a starter template
def cmd_export(args: str, state, config)
Export conversation history to a file. /export — export as markdown to .dulus/exports/ /export <filename> — export to a specific file (.md or .json)
def cmd_copy(args: str, state, config)
Copy the last assistant response to clipboard. /copy — copy last assistant message to clipboard
def cmd_status(args: str, state, config)
Show current session status. /status — model, provider, permissions, session info
def cmd_doctor(args: str, state, config)
Diagnose installation health and connectivity. /doctor — run all health checks
def cmd_roundtable(args: str, _state, config)
Start a roundtable discussion among different models. /roundtable - Enter setup mode to define models /roundtable stop - Exit roundtable mode /roundtable proactive 3m - Auto-send 'ok ok' every 3m to keep the table alive /roundtable proactive off - Disable roundtable proacti
def cmd_batch(args: str, _state, config)
Manage Kimi Batch tasks. /batch status [id] — check progress /batch list — list recent batch jobs /batch fetch [id] — download results when completed
def handle_slash(line: str, state, config)
Handle /command [args]. Returns True if handled, tuple (skill, args) for skill match.
def setup_readline(history_file: Path)
def repl(config: dict, initial_prompt: str = None)
def main()
Imports
__future__argparse atexit common datetime input json license_manager os pathlib re sys textwrap threading time tools traceback typing uuid
▶dulus_gui.py 264 LOC
Dulus GUI Entry Point — professional desktop interface. Usage: python dulus_gui.py python dulus.py --gui
Classes (1)
class _PermissionDialog
Modal permission request dialog centered on the parent.
↳ __init__(self, parent: ctk.CTk, description: str, on_resolve: Callable[[bool], None])
↳ _create_ui(self, description: str)
↳ _setup_window(self, parent: ctk.CTk)
↳ _allow(self)
↳ _deny(self)
Functions (3)
def _center_on_parent(dialog: ctk.CTkToplevel, parent: ctk.CTk)
Center a Toplevel over its parent window.
def launch_gui(config: dict | None = None, initial_prompt: str | None = None)
Launch the Dulus desktop GUI. Args: config: Dulus configuration dict (loaded from disk if None). initial_prompt: Optional initial user message to send on startup.
def main()
CLI entry point.
Imports
__future__config datetime gui gui.themes pathlib queue sys traceback typing
▶gui/__init__.py 18 LOC
Dulus GUI package — professional desktop interface.
Imports
gui.agent_bridge gui.chat_widget gui.main_window gui.settings_dialog gui.sidebar gui.tasks_view gui.tool_panel
▶gui/agent_bridge.py 253 LOC
Bridge between the GUI and Dulus's core agent engine. Handles AgentState, config, threaded execution, MemPalace injection, skill injection, and permission requests. Based on Nayeli's design.
Classes (1)
class DulusBridge
Thread-safe bridge between GUI and Dulus core. Runs the agent loop in a background thread and streams events back to the UI via an internal event queue (poll from GUI thread).
↳ __init__(self, config: dict | None = None)
↳ start(self)
Start the background worker thread.
↳ stop(self)
Clean shutdown of the bridge worker thread.
↳ send_message(self, text: str)
Enqueue a user message to be processed by the agent.
↳ stop_generation(self)
Signal the current generation to stop as soon as possible.
↳ grant_permission(self, granted: bool)
Respond to a pending permission request.
↳ get_context_usage(self)
Return (tokens_used, token_limit).
↳ clear_session(self)
Reset the agent state (new conversation).
↳ inject_skill(self, skill_body: str)
Inject skill context into the next user message (one-shot).
↳ set_model(self, model: str)
Change the active model.
↳ _worker_loop(self)
↳ _process_turn(self, user_message: str)
↳ _apply_mempalace(self, user_input: str)
Copy of dulus.py MemPalace injection logic.
↳ _emit(self, event_type: str, **kwargs)
Put an event into the public event queue.
Imports
__future__agent common config context mcp.tools memory.tools multi_agent.tools pathlib queue skill.tools task.tools threading tools
▶gui/chat_widget.py 365 LOC
Chat display widget for Dulus GUI. Provides a scrollable chat view with message bubbles, markdown-like rendering, code blocks with copy buttons, tool execution pills, and a typing indicator.
Classes (1)
class ChatWidget
Scrollable chat widget with message bubbles and rich formatting.
↳ __init__(self, master, on_copy_callback: Callable | None = None, **kwargs)
↳ add_user_message(self, text: str)
Add a user message bubble on the right.
↳ add_assistant_message(self, text: str)
Start a new assistant message bubble on the left.
↳ append_to_last_message(self, text: str)
Append text to the current assistant bubble (streaming).
↳ add_tool_indicator(self, name: str, status: str = 'running')
Add a small inline pill showing a tool execution.
↳ show_thinking(self)
Show the 'thinking' indicator at the bottom.
↳ hide_thinking(self)
Hide the thinking indicator.
↳ clear_chat(self)
Remove all messages and reset state.
↳ apply_theme(self)
Re-apply current theme colors to existing widgets.
↳ _hide_thinking(self)
↳ _finish_current_stream(self)
Lock the current bubble so future appends start a new one.
↳ _scroll_to_bottom(self)
Auto-scroll to the latest message.
↳ _create_bubble(self, text: str, is_user: bool, timestamp: str)
Create a message bubble frame with formatted text widget inside.
↳ _adjust_text_height(self, txt: ctk.CTkTextbox)
Dynamic height based on content lines.
↳ _render_formatted(self, txt: ctk.CTkTextbox, text: str)
Parse and insert markdown-like formatting into a CTkTextbox. NOTE: CTkTextbox forbids 'font' in tag_config, so we use colors only.
↳ _insert_code_block(self, txt: ctk.CTkTextbox, code: str, lang: str = '')
Insert a code block with a dark background and copy button.
↳ _insert_inline_formatted(self, txt: ctk.CTkTextbox, text: str)
Process inline bold, italic, and inline code within a text segment.
Functions (1)
def _sanitize_markdown(text: str)
Escape HTML-like chars so tkinter Text widget stays safe.
Imports
__future__datetime gui.themes re tkinter typing
▶gui/main_window.py 576 LOC
Dulus Main Window — customtkinter desktop GUI. Provides a professional dark-themed interface with sidebar, chat area, input bar, and top controls. Designed to be wired to a backend bridge by another agent.
Classes (1)
class DulusMainWindow
Main Dulus application window.
↳ __init__(self)
↳ _build_sidebar(self)
↳ _build_main_area(self)
↳ _on_send_click(self)
↳ _on_enter_key(self, event = None)
↳ _on_shift_enter(self, event = None)
↳ _on_new_chat_click(self)
↳ _on_settings_click(self)
↳ _on_model_change(self, model: str)
↳ _on_voice_click(self)
↳ _on_attach_click(self)
↳ _toggle_tasks_view(self)
↳ _show_tasks_view(self)
↳ _show_chat_view(self)
↳ set_status(self, text: str, color: str = TEXT_DIM)
Update the status label and dot color.
↳ set_model(self, model: str)
Set the model selector value.
↳ set_sessions(self, sessions: list[dict])
Populate the sidebar session list. sessions: list of dicts with 'id' and 'title' keys.
↳ set_active_session(self, session_id: str | None)
Mark a session as active in the sidebar.
↳ _highlight_active_session(self)
Update sidebar button styling to show active session.
↳ _on_session_select(self, session_id: str)
↳ show_thinking(self)
Show assistant thinking indicator.
↳ hide_thinking(self)
Hide thinking indicator.
↳ add_assistant_chunk(self, text: str)
Append streaming text to the current assistant message.
↳ add_tool_call(self, name: str, status: str = 'running')
Show a tool execution pill.
↳ focus_input(self)
Move focus to the input box.
↳ apply_theme(self, theme_name: str)
Apply a color theme to the main window widgets.
↳ run(self)
Start the main loop.
Imports
__future__gui.chat_widget gui.tasks_view gui.themes tkinter typing
▶gui/personas.py 230 LOC
Persona system for Dulus GUI. Loads the canonical persona definitions from .dulus-context/personas.json and provides helpers for retrieving persona data and rendering cards in customtkinter interfaces.
Classes (1)
class PersonaCard
A small card widget that displays a single persona's identity. Usage:: card = PersonaCard(parent, persona=get_persona("kimi-code")) card.pack(padx=10, pady=10, fill="both", expand=True)
↳ __init__(self, master: Any, persona: dict[str, Any], width: int = 340, height: int = 280, **kwargs)
↳ _build(self)
Functions (6)
def _load_json(path: Path | str | None = None)
Load and cache personas.json. Raises FileNotFoundError if missing.
def reload()
Force reload personas.json from disk and return the raw data.
def get_all_personas(path: Path | str | None = None)
Return all persona definitions as a list of dicts.
def get_persona(persona_id: str, path: Path | str | None = None)
Return a single persona by its ``id`` (e.g. ``'kevrojo'``).
def get_color_for_agent(agent_name: str, path: Path | str | None = None)
Return the hex color for an agent name/id (case-insensitive). Falls back to the default Dulus accent ``#ff6b1f`` if unknown.
def get_display_name(agent_name: str, path: Path | str | None = None)
Return the pretty display name for an agent, or the raw name as fallback.
Imports
__future__json os pathlib typing
▶gui/settings_dialog.py 146 LOC
Settings popup for Dulus GUI.
Classes (1)
class SettingsDialog
Floating settings window.
↳ __init__(self, master, config: dict)
↳ _save(self)
Functions (1)
def _build_model_list()
Build list of provider/model strings from PROVIDERS registry.
Imports
__future__config customtkinter gui.themes os typing
▶gui/sidebar.py 392 LOC
Left sidebar panel for Dulus GUI. Provides session history, model selector, context-usage bar, quick-command buttons, available-tools list, and version info.
Classes (1)
class DulusSidebar
Left sidebar with session history, model selector, context bar, tools, and quick commands.
↳ __init__(self, master, bridge = None, on_new_chat: Callable[[], None] | None = None, on_command: Callable[[str], None] | None = None, on_model_change: Callable[[str], None] | None = None, **kwargs)
↳ _build_ui(self)
↳ _refresh_sessions(self)
Load session history from ~/.dulus/sessions/.
↳ _refresh_tools(self)
Populate the tools list from the registry.
↳ _refresh_model_list(self)
Populate the model dropdown from providers.
↳ update_context_bar(self)
Refresh the context usage progress bar (call from UI thread).
↳ _on_new_chat_click(self)
↳ _on_command_click(self, cmd: str)
↳ _on_model_change(self, model: str)
↳ _on_session_click(self, path: str)
Imports
__future__config json os pathlib providers tool_registry typing
▶gui/tasks_view.py 439 LOC
Dulus Tasks View — professional Kanban-style task board v2. Reads tasks from .dulus-context/tasks.json and displays them in a three-column layout: Pending | In Progress | Completed. v2 improvements: - Filter by owner (agent) and phase (week) - Priority badges (CRITICAL/HIGH/MEDIUM/LOW) - Agent color coding - Auto-refresh via file polling - Phase grouping separators - Owner summary stats
Classes (2)
class TaskCard
A single task card widget with priority, agent color, and phase.
↳ __init__(self, master, task: dict, **kwargs)
↳ _build(self)
↳ _toggle_expand(self)
class TasksView
Professional Kanban task board for Dulus with filters and auto-refresh.
↳ __init__(self, master, tasks_file: Path | str | None = None, **kwargs)
↳ _build_ui(self)
↳ _create_column(self, parent, col: int, title: str, color: str, status_key: str)
↳ _load_tasks(self)
↳ _matches_filters(self, task: dict)
↳ refresh(self)
↳ _check_file_changed(self)
↳ _start_polling(self)
↳ destroy(self)
Functions (1)
def _fmt_date(iso: str)
Imports
__future__datetime json os pathlib threading typing
▶gui/themes.py 137 LOC
Theme system for Dulus GUI. Provides multiple color presets that can be switched at runtime.
Functions (3)
def get_theme()
Return the currently active theme colors.
def set_theme(name: str)
Activate a theme by name. Returns the theme dict or None if unknown.
def list_themes()
Return available theme names.
Imports
__future__
▶gui/tool_panel.py 94 LOC
Side panel showing active tool executions.
Classes (1)
class ToolPanel
Panel that displays running and completed tools.
↳ __init__(self, master, **kwargs)
↳ add_tool(self, name: str, status: str = 'running')
↳ update_tool(self, name: str, status: str = 'done', result: str = '')
↳ clear_tools(self)
Imports
__future__customtkinter
▶input.py 767 LOC
prompt_toolkit-based REPL input with typing-time slash-command autosuggest. Optional dependency: when prompt_toolkit is not installed, HAS_PROMPT_TOOLKIT is False and callers should fall through to readline-based input. Dependency-injected: callers register command/meta providers via setup() before calling read_line(). This module never imports Dulus core — keeping the dependency one-way and eliminating any circular-import risk.
Classes (1)
class _OutputRedirector
Redirects stdout to the split layout output buffer. Thread-safe: multiple threads (main REPL, Telegram bg runner, sentinel) may write concurrently. A lock prevents buffer corruption.
↳ __init__(self, original)
↳ write(self, text: str)
↳ flush(self)
↳ isatty(self)
Functions (15)
def setup(commands_provider: Callable[[], dict], meta_provider: Callable[[], dict])
Register providers for the live command registry and metadata. `commands_provider` returns the dispatcher's COMMANDS dict. `meta_provider` returns the _CMD_META dict (descriptions + subcommands).
def reset_session()
Drop the cached session so the next read_line() rebuilds from scratch.
def _build_session(history_path: Optional[Path])
def read_line(prompt_ansi: str, history_path: Optional[Path] = None)
Read one line of input via prompt_toolkit; caches the session across calls. The history file passed here MUST NOT be the readline history file — the two line-editors use incompatible formats. See Dulus REPL for the dedicated PT_HISTORY_FILE.
def set_hide_sender(enabled: bool)
Toggle whether the typed message gets echoed above the sticky bar.
def _count_deduped_recent()
Count non-consecutive-duplicate entries in _RECENT_USER_MSGS (same key as render).
def add_recent_msg(text: str)
Push a user message into the recent-history strip (sliding window).
def read_line_split(prompt: str = '> ', history_path: Optional[Path] = None)
Read input with split layout - fixed bottom bar, scrollable output above. Similar to Kimi Code and Claude Code interfaces.
def append_output(text: str)
Append text to the output buffer (for split layout mode). Use this to display messages without interrupting the input bar.
def clear_split_output()
Clear the split layout output buffer.
def set_stdout_bypass(active: bool)
Temporarily bypass the _OutputRedirector and write directly to the real terminal. Call with active=True before a background turn, active=False after. This makes background output look identical to NOTIFICATION SYSTEM NEEDED — no fragmentation, no ^M/^J, because the real terminal handles \r natively
def set_notification_callback(callback: Callable[[str], None])
Register a callback to handle background notifications. The callback will be called with the notification text when it's safe to display (during the next input cycle or when input is not active).
def queue_notification(text: str)
Queue a notification to be displayed safely. This should be used by background threads (timers, jobs, etc.) to display messages without corrupting the prompt_toolkit input bar.
def drain_notifications()
Drain all pending notifications from the queue. Returns a list of notification texts. Should be called when it's safe to display output (e.g., before showing a new prompt).
def safe_print_notification(text: str)
Print a notification in a prompt_toolkit-safe way. If split layout is active, uses append_output. Otherwise prints directly (which may cause display issues in sticky mode).
Imports
__future__pathlib queue threading time typing
▶kimi_batch.py 354 LOC
Classes (1)
class KimiBatchManager
Manages the lifecycle of Kimi (Moonshot AI) Batch API tasks.
↳ __init__(self, api_key: str)
↳ _headers(self)
↳ prepare_jsonl(self, prompts: List[str], model: str = 'kimi-k2.5', system_prompt: str = None)
Converts a list of prompts into JSONL content for Kimi Batch API. Args: prompts: List of user prompts to batch. model: Model to use for each request. system_prompt: Optional system prompt
↳ upload_file(self, jsonl_content: str, filename: str = 'batch_input.jsonl')
Uploads JSONL content to Kimi and returns file_id.
↳ create_batch(self, file_id: str, endpoint: str = '/v1/chat/completions', completion_window: str = '24h')
Creates a batch task from an uploaded file.
↳ retrieve_batch(self, batch_id: str)
Gets info about a batch task.
↳ cancel_batch(self, batch_id: str)
Cancels a batch task.
↳ get_file_content(self, file_id: str)
Downloads the content of a file (e.g., batch results).
Functions (4)
def save_batch_job(batch_id: str, description: str = '', file_id: str = '')
Saves a batch job record locally in ~/.dulus/jobs/.
def list_batch_jobs(include_pollers: bool = True, api_key: str = None)
Lists saved batch jobs from ~/.dulus/jobs/. Args: include_pollers: If True, also includes completed poller jobs and syncs their status api_key: Optional API key to fetch real-time status from Kimi API
def update_batch_job_status(batch_id: str, status_info: Dict[str, Any])
Updates a batch job's status in its local file.
def get_batch_job_by_id(batch_id: str)
Gets a batch job by ID, checking both kimi_batch and poller jobs.
Imports
json os time typing urllib.request
▶license_keygen.py 45 LOC
Dulus License Key Generator — Standalone CLI. Usage: python license_keygen.py pro --days 30 --qty 5 python license_keygen.py enterprise --days 365 --output keys.txt
Functions (1)
def main()
Imports
argparse license_manager pathlib sys
▶license_manager.py 187 LOC
Dulus License Manager — Offline-first key validation + feature gating. Tiers: FREE No key required. Limited tool calls, local providers only. PRO $15/mo. Full features, BYOK, priority support. ENTERPRISE $50/mo. Team features + admin dashboard + SSO (future). Key format (offline): DULUS-<base64(json_payload + ":" + hmac_signature)> The secret lives in ~/.dulus/.license_secret (never commit this file). If the secret file is missing we fall back to a hardcoded dev-ke
Classes (2)
class LicenseTier
class LicenseManager
Parse and validate a Dulus license key.
↳ __init__(self, key: Optional[str] = None)
↳ _validate(self)
↳ can_use(self, feature: str)
Check if a feature is allowed by current tier.
↳ max_tool_calls(self)
↳ max_providers(self)
↳ max_subagents(self)
↳ max_plugins(self)
↳ allow_cloudsave(self)
↳ allow_voice(self)
↳ allow_telegram(self)
↳ allow_mcp(self)
↳ status_banner(self)
Functions (1)
def _generate_key(tier: str, days: int, secret: str)
Generate a signed license key (Kev-only tool).
Imports
__future__base64 hashlib hmac json os pathlib sys time typing
▶license_server.py 212 LOC
Dulus License Server — HTTP API para validación y revocación de keys. Sin dependencias externas (solo stdlib: http.server, json, pathlib). Corré: python license_server.py Endpoints: POST /validate → {"key": "DULUS-..."} → {"valid": true/false, "tier": "pro"} POST /revoke → {"key": "DULUS-...", "admin_secret": "..."} → {"revoked": true} GET /metrics → {"total_validated": N, "revoked_count": M, "by_tier": {...}} Para producción: copiar cf_worker.js a Cloudflare Workers (gratis 1
Classes (1)
class LicenseHandler
↳ log_message(self, fmt, *args)
↳ _send_json(self, data: dict, status = 200)
↳ _read_json(self)
↳ do_GET(self)
↳ do_POST(self)
Functions (5)
def _load_db()
def _save_db()
def _verify_payload(payload_b64: str, sig: str, secret: str)
Verify HMAC-SHA256 signature using RAW secret (same as license_manager.py).
def parse_key(key: str)
Parsea una key DULUS-*.
def main(port: int = 8787)
Imports
__future__hashlib hmac http.server json os pathlib time
▶mcp/__init__.py 43 LOC
mcp package — Model Context Protocol client for dulus. Usage ----- MCP servers are configured in one of two JSON files: ~/.dulus/mcp.json (user-level, all projects) .mcp.json (project-level, current dir, overrides user) Format: { "mcpServers": { "my-git-server": { "type": "stdio", "command": "uvx", "args": ["mcp-server-git"] }, "my-remote": { "type": "sse", "url": "http://loca
Imports
client config tools types
▶mcp/client.py 546 LOC
MCP client: stdio and HTTP/SSE transports, JSON-RPC 2.0 protocol.
Classes (4)
class StdioTransport
Bidirectional JSON-RPC over a subprocess's stdin/stdout. Messages are newline-delimited JSON objects (one per line). Responses are matched to requests by 'id'.
↳ __init__(self, config: MCPServerConfig)
↳ start(self)
↳ _read_loop(self)
↳ _stderr_loop(self)
↳ _send_raw(self, msg: dict)
↳ request(self, method: str, params: Optional[dict] = None, timeout: Optional[int] = None)
Send a JSON-RPC request and wait for the response.
↳ notify(self, method: str, params: Optional[dict] = None)
Send a JSON-RPC notification (no response expected).
↳ stop(self)
↳ alive(self)
↳ stderr_output(self)
class HttpTransport
HTTP-based MCP transport (POST-based streamable HTTP or SSE endpoint). For SSE servers: sends messages via POST to the SSE session endpoint. For HTTP servers: sends messages via POST and reads response directly.
↳ __init__(self, config: MCPServerConfig)
↳ _get_client(self)
↳ start(self)
For SSE transport: connect to the /sse endpoint and get session URL.
↳ _start_sse(self)
Open SSE stream to get session endpoint, then start background reader.
↳ request(self, method: str, params: Optional[dict] = None, timeout: Optional[int] = None)
↳ notify(self, method: str, params: Optional[dict] = None)
↳ stop(self)
↳ alive(self)
class MCPClient
Manages the lifecycle of one MCP server connection. Protocol flow: connect() → initialize handshake → notifications/initialized list_tools() → tools/list call_tool() → tools/call disconnect() → cleanup
↳ __init__(self, config: MCPServerConfig)
↳ connect(self)
↳ _make_transport(self)
↳ _handshake(self)
↳ disconnect(self)
↳ reconnect(self)
↳ alive(self)
↳ list_tools(self)
Fetch tool list from server and cache as MCPTool objects.
↳ _parse_tool(self, raw: dict)
↳ call_tool(self, tool_name: str, arguments: dict)
Call a tool by its original (non-qualified) name. Returns the text content from the response, or an error string.
↳ status_line(self)
class MCPManager
Singleton that manages all configured MCP server connections.
↳ __init__(self)
↳ add_server(self, config: MCPServerConfig)
Register a server. Replaces any existing client with the same name.
↳ connect_all(self)
Connect to all registered servers. Returns {name: error_or_None}.
↳ connect_server(self, name: str)
Connect (or reconnect) a single server by name.
↳ all_tools(self)
Return all tools from all connected servers.
↳ call_tool(self, qualified_name: str, arguments: dict)
Dispatch a tool call by qualified name (mcp__server__tool).
↳ list_servers(self)
↳ disconnect_all(self)
↳ reload_server(self, name: str)
Functions (1)
def get_mcp_manager()
Imports
__future__json os subprocess threading time types typing
▶mcp/config.py 133 LOC
Load MCP server configs from .mcp.json files (project + user level). Config search order (project-level overrides user-level by server name): 1. ~/.dulus/mcp.json — user-level, lowest priority 2. <cwd>/.mcp.json — project-level, highest priority File format (matches Claude Code's .mcp.json format): { "mcpServers": { "my-server": { "type": "stdio", "command": "uvx", "args": ["mcp-server-git", "--repository", ".
Functions (6)
def _load_file(path: Path)
Read a single mcp.json file and return the mcpServers dict.
def load_mcp_configs()
Return all MCP server configs, project-level overriding user-level.
def save_user_mcp_config(servers: Dict[str, dict])
Write (or update) the user-level MCP config file.
def add_server_to_user_config(name: str, raw: dict)
Append or update one server entry in the user MCP config.
def remove_server_from_user_config(name: str)
Remove a server from the user MCP config. Returns True if found.
def list_config_files()
Return paths of all mcp.json config files that exist.
Imports
__future__json pathlib types typing
▶mcp/tools.py 131 LOC
Register MCP tools into the central tool_registry. Importing this module: 1. Loads .mcp.json config files 2. Connects to each configured MCP server 3. Discovers tools from each server 4. Registers each tool into tool_registry so Claude can use them MCP tool qualified names follow the pattern: mcp__<server_name>__<tool_name> This matches the Claude Code convention (mcp__serverName__toolName).
Functions (7)
def _make_mcp_func(qualified_name: str)
Return a tool func that calls the MCP server for a given qualified name.
def _register_tool(tool: MCPTool)
def initialize_mcp(verbose: bool = False)
Load configs, connect servers, register tools. Idempotent. Returns a dict of {server_name: error_message_or_None}.
def reload_mcp()
Force a full reload: re-read configs, reconnect, re-register all tools.
def refresh_server(server_name: str)
Reconnect a single server and re-register its tools. Returns error or None.
def get_connect_errors()
def _background_init()
Imports
__future__client config threading tool_registry types typing
▶mcp/types.py 124 LOC
MCP type definitions: server configs, tool descriptors, connection state.
Classes (4)
class MCPTransport
class MCPServerConfig
Configuration for a single MCP server. Mirrors the Claude Code schema (types.ts) for the two most useful transports. Stdio example: {"type": "stdio", "command": "uvx", "args": ["mcp-server-git"]} SSE/HTTP example: {"type": "sse", "url": "http://localhost:8080/sse", "headers": {"Autho
↳ from_dict(cls, name: str, d: dict)
class MCPServerState
class MCPTool
A tool provided by an MCP server, ready to register in tool_registry.
↳ to_tool_schema(self)
Convert to the schema format expected by the Claude API.
Functions (2)
def make_request(method: str, params: Optional[dict], req_id: int)
def make_notification(method: str, params: Optional[dict] = None)
Imports
__future__dataclasses enum typing
▶memory/__init__.py 92 LOC
Memory package for dulus. Provides persistent, file-based memory across conversations. Storage layout: user scope : ~/.dulus/memory/<slug>.md (shared across projects) project scope : .dulus/memory/<slug>.md (local to cwd) The MEMORY.md index in each directory is auto-maintained and injected into the system prompt so Claude has an overview of available memories. Public API (backward-compatible with the old memory.py module): MemoryEntry — dataclass for a sin
Imports
consolidator context palace scan store types
▶memory/audit.py 51 LOC
Audit trail for Dulus RTK — logs all tool operations.
Functions (4)
def _ensure_dir()
def log_operation(tool_name: str, params: Dict[str, Any], result_preview: str = '')
Log a tool operation with timestamp.
def _trim_audit()
Keep audit file under max lines.
def get_recent(n: int = 50)
Return last N audit entries.
Imports
__future__json pathlib time typing
▶memory/consolidator.py 170 LOC
Memory consolidator: extract long-term insights from completed sessions. Called manually via `/memory consolidate` or programmatically after a session. Uses a lightweight AI call to identify user preferences, feedback corrections, and project decisions worth promoting to persistent semantic memory. Design principles: - Hard cap of 3 memories per session to avoid noise accumulation - Auto-extracted memories start at 0.8 confidence (below explicit user saves) - Won't overwrite a higher-confidenc
Functions (1)
def consolidate_session(messages: list, config: dict)
Analyze a session's messages and extract memories worth keeping long-term.
Imports
__future__datetime
▶memory/context.py 246 LOC
Memory context building for system prompt injection. Provides: get_memory_context() — full context string for system prompt find_relevant_memories() — keyword (+ optional AI) relevance filtering truncate_index_content() — line + byte truncation with warning
Functions (4)
def truncate_index_content(raw: str)
Truncate MEMORY.md content to line AND byte limits, appending a warning. Matches Claude Code's truncateEntrypointContent: - Line-truncates first (natural boundary) - Then byte-truncates at the last newline before the cap - Appends which limit fired
def get_memory_context(include_guidance: bool = False)
Return memory context for injection into the system prompt. Combines user-level and project-level MEMORY.md content (if present). Returns empty string when no memories exist. Args: include_guidance: if True, prepend the full memory system guidance (MEMORY_SYSTEM_PROMPT).
def find_relevant_memories(query: str, max_results: int = 5, use_ai: bool = False, config: dict | None = None)
Find memories relevant to a query. Strategy: 1. Always: keyword match on name + description + content 2. If use_ai=True and config has a model: use a small AI call to rank Returns: List of dicts with keys: name, description, type, scope, content, file_path, mtime_s, freshness_text
def _ai_select_memories(query: str, candidates: list, max_results: int, config: dict)
Use a fast AI call to select the most relevant memories from candidates. Falls back to keyword results on any error.
Imports
__future__pathlib scan store types
▶memory/offload.py 148 LOC
Tmux Offload tool implementation for backgrounding heavy tasks.
Functions (2)
def _tmux_offload(params: dict, config: dict)
Implement the TmuxOffload tool.
def register_offload_tool()
Imports
__future__datetime json os pathlib tmux_tools tool_registry uuid
▶memory/palace.py 127 LOC
Memory Palace: Day 1 initialization of essential long-term memory buckets.
Functions (1)
def ensure_memory_palace()
Check if the user memory directory is empty/new and initialize default buckets. Returns: True if initialization was performed, False otherwise.
Imports
__future__datetime pathlib store
▶memory/scan.py 146 LOC
Memory file scanning with mtime tracking and freshness/age helpers. Mirrors the key ideas from Claude Code's memoryScan.ts and memoryAge.ts: - Scan memory directories, sort newest-first - Format a manifest for display or AI relevance selection - Report memory age in human-readable form ("today", "3 days ago") - Emit a staleness caveat for memories older than 1 day
Classes (1)
class MemoryHeader
Lightweight descriptor loaded from a memory file's frontmatter. Attributes: filename: basename of the .md file file_path: absolute path mtime_s: modification time (seconds since epoch) description: value from frontmatter `description:` field type: value from fron
Functions (6)
def scan_memory_dir(mem_dir: Path, scope: str)
Scan a single memory directory and return headers sorted newest-first. Reads only the frontmatter (first ~30 lines) for efficiency. Silently skips unreadable files. Caps at MAX_MEMORY_FILES entries.
def scan_all_memories()
Scan both user and project memory directories, merged newest-first.
def memory_age_days(mtime_s: float)
Days since mtime_s (floor-rounded, clamped to 0 for future times).
def memory_age_str(mtime_s: float)
Human-readable age: 'today', 'yesterday', or 'N days ago'.
def memory_freshness_text(mtime_s: float)
Staleness caveat for memories older than 1 day (empty string if fresh). Motivated by user reports of stale code-state memories (file:line citations to code that has since changed) being asserted as fact.
def format_memory_manifest(headers: list[MemoryHeader])
Format a list of MemoryHeader as a text manifest. Format per line: [type/scope] filename (age): description Example: [feedback/user] feedback_testing.md (3 days ago): Don't mock DB in tests [project/project] project_freeze.md (today): Merge freeze until 2026-04-10
Imports
__future__dataclasses math pathlib store time
▶memory/sessions.py 100 LOC
Historical session search utility.
Functions (1)
def search_session_history(query: str, max_results: int = 5)
Search for a query string across historical session logs. Checks both history.json (master) and daily/ copier directories. Returns list of hits: {session_id, saved_at, hits: [{role, content_snippet}]}.
Imports
__future__config datetime json pathlib
▶memory/store.py 392 LOC
File-based memory storage with user-level and project-level scopes. Storage layout: user scope : ~/.dulus/memory/<slug>.md project scope : .dulus/memory/<slug>.md (relative to cwd) Search uses token-based fuzzy matching with field weighting (name 3×, description 2×, content 1×) for better recall than simple substring matching. MEMORY.md in each directory is the index file — rebuilt automatically after every save/delete. It is loaded into the system prompt to give Dulus
Classes (1)
class MemoryEntry
A single memory entry loaded from a .md file. Attributes: name: human-readable name (also the display title in the index) description: short one-line description (used for relevance decisions) type: "user" | "feedback" | "project" | "reference" hall:
Functions (16)
def get_project_memory_dir()
Return the project-local memory directory (relative to cwd).
def get_memory_dir(scope: str = 'user')
Return the memory directory for the given scope. Args: scope: "user" (global ~/.dulus/memory) or "project" (.dulus/memory relative to cwd)
def _slugify(name: str)
Convert name to a filesystem-safe slug (max 60 chars).
def parse_frontmatter(text: str)
Parse ---\nkey: value\n---\nbody format. Returns: (meta_dict, body_str)
def _format_entry_md(entry: MemoryEntry)
Render a MemoryEntry as a markdown file with YAML frontmatter.
def save_memory(entry: MemoryEntry, scope: str = 'user')
Write/update a memory file and rebuild the index for that scope. If a memory with the same name (slug) already exists, it is overwritten. Args: entry: MemoryEntry to persist scope: "user" or "project"
def delete_memory(name: str, scope: str = 'user')
Remove the memory file matching name and rebuild the index. No error if not found.
def load_entries(scope: str = 'user')
Scan all .md files (except MEMORY.md) in a scope and return entries. Returns: List of MemoryEntry sorted alphabetically by name.
def load_index(scope: str = 'all')
Load memory entries from one or both scopes. Args: scope: "user", "project", or "all" (both combined) Returns: List of MemoryEntry (user entries first, then project).
def _tokenize(text: str)
Split text into lowercase tokens (words).
def _token_score(query_tokens: list[str], text: str)
Score how well query tokens match a text field. For each query token, find the best match among text tokens using SequenceMatcher (handles typos, partial matches, synonyms-by-prefix). Returns average best-match ratio (0.0–1.0).
def search_memory(query: str, scope: str = 'all', hall: str = '', min_score: float = 0.35)
Token-based fuzzy search on name + description + content. Scores each memory using weighted field matching: name × 3.0 + description × 2.0 + content × 1.0 Args: query: search query string scope: "user", "project", or "all" hall: optional hall filter ("facts", "events", e
def _rewrite_index(scope: str)
Rebuild MEMORY.md for the given scope from all .md files in that dir.
def get_index_content(scope: str = 'user')
Return raw MEMORY.md content for the given scope, or '' if absent.
def check_conflict(entry: 'MemoryEntry', scope: str = 'user')
Check whether a same-named memory already exists with different content. Returns a dict with the existing memory's key fields if a conflict is found, or None if no existing file or if the content is identical.
def touch_last_used(file_path: str)
Update the last_used_at frontmatter field of a memory file to today. Called by MemorySearch when a memory is returned so staleness/utility tracking stays current. Silent on any error.
Imports
__future__dataclasses difflib pathlib re unicodedata
▶memory/tools.py 410 LOC
Memory tool registrations: MemorySave, MemoryDelete, MemorySearch. Importing this module registers the three tools into the central registry.
Functions (4)
def _memory_save(params: dict, config: dict)
Save or update a persistent memory entry, with conflict detection.
def _memory_delete(params: dict, config: dict)
Delete a persistent memory entry by name.
def _memory_search(params: dict, config: dict)
Search memories by keyword query with optional AI relevance filtering. Results are ranked by: confidence × recency (30-day exponential decay).
def _memory_list(params: dict, config: dict)
List all memory entries with type, scope, age, confidence, and description.
Imports
__future__context datetime scan sessions store tool_registry
▶memory/types.py 114 LOC
Memory type and hall taxonomy with system-prompt guidance text. Four types capture context NOT derivable from the current project state. Code patterns, architecture, git history, and file structure are derivable (via grep/git/CLAUDE.md) and should NOT be saved as memories. Halls categorize memories by their nature (orthogonal to type): facts, events, discoveries, preferences, advice.
▶memory/vector_search.py 92 LOC
Vector search for memories using TF-IDF (pure Python, zero deps).
Functions (4)
def _tokenize(text: str)
def _tfidf_vectors(docs: List[str])
def _cosine(a: Counter, b: Counter)
def search_similar_memories(query: str, memories: List[Tuple[str, str]], top_k: int = 5)
Search memories by semantic similarity. Args: query: search query text memories: list of (id, content) tuples top_k: number of results to return Returns: list of (memory_id, score) sorted by relevance
Imports
__future__collections math re typing
▶memory.py 11 LOC
Backward-compatibility shim — real implementation is in memory/ package.
Imports
memory.context memory.store
▶molt_executor.py 97 LOC
Molt Executor — Master Version (Merged v1→v4) Unified comment poster for Moltbook.
Functions (1)
def fire(post_id: str, content: str, mission_name: str = 'X', out_file: str = None, author: str = None)
Dispara un comentario a Moltbook. Retorna True si impacta.
Imports
json os sys urllib.request warnings
▶molt_m3.py 37 LOC
Imports
json os urllib.request
▶multi_agent/__init__.py 23 LOC
Multi-agent package for dulus. Provides: - AgentDefinition — typed agent definition (name, system_prompt, model, tools) - SubAgentTask — lifecycle-tracked task - SubAgentManager — thread-pool manager for spawning agents - load_agent_definitions / get_agent_definition — agent registry
Imports
subagent
▶multi_agent/subagent.py 501 LOC
Threaded sub-agent system for spawning nested agent loops.
Classes (3)
class AgentDefinition
Definition for a specialized agent type.
class SubAgentTask
Represents a sub-agent task with lifecycle tracking.
class SubAgentManager
Manages concurrent sub-agent tasks using a thread pool.
↳ __init__(self, max_concurrent: int = 5, max_depth: int = 5)
↳ spawn(self, prompt: str, config: dict, system_prompt: str, depth: int = 0, agent_def: Optional[AgentDefinition] = None, isolation: str = '', name: str = '')
Spawn a new sub-agent task. Args: prompt: user message for the sub-agent config: agent configuration dict (copied before modification) system_prompt: base system prompt de
↳ wait(self, task_id: str, timeout: float = None)
Block until a task completes or timeout expires. Returns: The task, or None if task_id is unknown.
↳ get_result(self, task_id: str)
Return the result string for a completed task, or None.
↳ list_tasks(self)
Return all tracked tasks.
↳ send_message(self, task_id_or_name: str, message: str)
Send a message to a running background agent. If the agent is currently blocked on an AskMainAgentQuestion call, the message is delivered immediately as the answer and the agent resumes the SAME turn
↳ cancel(self, task_id: str)
Request cancellation of a running task. Returns: True if the cancel flag was set, False if task not found or not running.
↳ shutdown(self)
Cancel all running tasks and shut down the thread pool.
Functions (8)
def _parse_agent_md(path: Path, source: str = 'user')
Parse a .md file with optional YAML frontmatter into an AgentDefinition. File format: --- description: "Short description" model: claude-haiku-4-5-20251001 tools: [Read, Write, Edit, Bash] --- System prompt body goes here...
def load_agent_definitions()
Load all agent definitions: built-ins → user-level → project-level. Search paths: ~/.dulus/agents/*.md (user-level) .dulus/agents/*.md (project-level, overrides user)
def get_agent_definition(name: str)
Look up an agent definition by name. Returns None if not found.
def _git_root(cwd: str)
Return the git root directory for cwd, or None if not in a git repo.
def _create_worktree(base_dir: str)
Create a temporary git worktree. Returns: (worktree_path, branch_name) Raises: subprocess.CalledProcessError or OSError on failure.
def _remove_worktree(wt_path: str, branch: str, base_dir: str)
Remove a git worktree and delete its branch (best-effort).
def _agent_run(prompt, state, config, system_prompt, depth = 0, cancel_check = None)
Lazy-import wrapper to avoid circular dependency with agent module. Uses absolute import so this works whether called from inside or outside the multi_agent package (sys.path includes the project root).
def _extract_final_text(messages)
Walk backwards through messages, return first assistant content string.
Imports
__future__concurrent.futures dataclasses os pathlib queue subprocess tempfile typing uuid
▶multi_agent/tools.py 393 LOC
Multi-agent tool registrations. Registers the following tools into the central tool_registry: Agent — spawn a sub-agent (always background) SendMessage — send a message to a named background agent CheckAgentResult — check status/result of a background agent ListAgentTasks — list all active/finished agent tasks ListAgentTypes — list available agent type definitions
Functions (7)
def get_agent_manager()
Return (and lazily create) the process-wide SubAgentManager.
def _agent_tool(params: dict, config: dict)
Spawn a sub-agent. Reads from config: _system_prompt — injected by agent.py run(), used as base system prompt _depth — current nesting depth (prevents infinite recursion)
def _send_message(params: dict, config: dict)
def _check_agent_result(params: dict, config: dict)
def _list_agent_tasks(params: dict, config: dict)
def _ask_main_agent_question(params: dict, config: dict)
Pause a sub-agent and ask the main agent a question. The sub-agent blocks on a threading.Event in its current turn (preserving full context). The main agent receives a system message naming the sub-agent and the question; it replies using SendMessage(to=<name>, ...).
def _list_agent_types(params: dict, config: dict)
Imports
__future__subagent tool_registry
▶New folder/context_engine/__init__.py 72 LOC
Smart Context Engine — Intelligent context management for Dulus RTK. Exports: SemanticChunker — Heuristic semantic chunking PriorityRanker — Multi-factor priority scoring HierarchicalSummarizer — Multi-level summarization (levels 0-3) ContextAssembler — Token-budget context assembly RelevanceScorer — Query-to-message relevance scoring SmartContextEngine — Drop-in replacement for compaction.py Chunk — Semantic c
Imports
context_engine.smart_context
▶New folder/context_engine/smart_context.py 1823 LOC
Smart Context Engine — Intelligent context management for Dulus RTK. Drop-in replacement for compaction.py with semantic chunking, priority ranking, hierarchical summarization, and relevance scoring. Pure Python stdlib — no external dependencies. Architecture: SemanticChunker → splits messages into coherent semantic chunks PriorityRanker → scores chunks/messages by importance HierarchicalSummarizer → multi-level summary (0-3) ContextAssembler → assembles final cont
Classes (9)
class Chunk
A semantic chunk of conversation messages.
↳ text_content(self)
Flatten all messages into a single text string.
class ChunkPriority
Priority scores for a single chunk.
class SummaryLevel
A summary at a given hierarchy level.
class SemanticChunker
Divide conversation messages into semantically coherent chunks. Uses heuristic boundary detection (role transitions, decision markers, tool call completions, explicit separators) without any external embeddings. Each chunk is self-contained and topic-labelled. Args: max_chunk_tokens: soft upp
↳ __init__(self, max_chunk_tokens: int = 800, min_chunk_tokens: int = 100)
↳ chunk_messages(self, messages: list[dict[str, Any]])
Split *messages* into semantic chunks. Algorithm: 1. Score every potential boundary (between consecutive messages). 2. Walk left→right, accumulating messages into a chunk. 3. When a bound
↳ _find_best_split(self, scores: list[float], start: int, end: int)
Find the highest-scoring boundary in [start, end].
↳ _create_chunk(self, messages: list[dict[str, Any]], start: int, end: int)
Build a Chunk from a slice of messages.
class PriorityRanker
Assign priority scores to chunks based on multiple signals. Signals (all normalised to [0, 1]): * recency — newer chunks score higher (exponential decay) * message_type — decisions / errors / tool calls > chat * references — chunks referenced by later chunks score higher * k
↳ __init__(self, weights: dict[str, float] | None = None)
↳ rank_chunks(self, chunks: list[Chunk])
Rank all chunks and return scored priorities. Returns: List of :class:`ChunkPriority` ordered by chunk index.
↳ get_top_chunks(self, chunks: list[Chunk], priorities: list[ChunkPriority] | None = None, top_k: int | None = None, min_score: float = 0.0)
Return the highest-priority chunks, sorted by score desc. Args: chunks: list of chunks priorities: pre-computed priorities (computed if None) top_k: max number of chunks to return min
↳ _recency_score(self, chunk_index: int, total: int)
Exponential decay from the most recent chunk. chunk_index=total-1 (most recent) → 1.0 chunk_index=0 (oldest) → ~0.1
↳ _type_score(self, chunk: Chunk)
Score based on message composition.
↳ _build_reference_map(self, chunks: list[Chunk])
Build a map of chunk_index → set of chunk indices that reference it. References are detected by: * File path overlap between chunks * Explicit mentions of "previous", "earlier", "above" *
↳ _reference_score(self, chunk_index: int, referenced_by: dict[int, set[int]], total: int)
Score based on how many later chunks reference this chunk.
↳ _keyword_score(self, chunk: Chunk)
Score based on presence of high-value keywords.
↳ _structural_score(self, chunk: Chunk)
Score based on structural importance.
class HierarchicalSummarizer
Multi-level summarizer that builds summaries at 4 hierarchy levels. Levels: 0 — Raw messages (most recent, kept verbatim) 1 — Chunk-level summaries (SemanticChunker output) 2 — Session summary (merges all Level-1 summaries) 3 — Cross-session summary (persists across sessions) LLM c
↳ __init__(self, llm_summarize_fn: 'callable | None' = None)
↳ summarize_chunks(self, chunks: list[Chunk], priorities: list[ChunkPriority] | None = None)
Produce Level-1 summaries from chunks. Each chunk gets a compact text summary. High-priority chunks keep more detail; low-priority chunks are heavily condensed. Args: chunks: semantic chunks fr
↳ summarize_session(self, level1_summaries: list[SummaryLevel], focus: str = '')
Merge Level-1 summaries into a single Level-2 session summary. If an LLM function was provided, delegates to it. Otherwise concatenates and truncates intelligently. Args: level1_summaries: list
↳ update_cross_session(self, session_summary: SummaryLevel, focus: str = '')
Merge a session summary into the persistent Level-3 cross-session store. Args: session_summary: a Level-2 session summary focus: optional focus hint Returns: Updated :class:`SummaryLevel`
↳ get_cross_session_summary(self)
Return the current cross-session summary text.
↳ get_session_summary(self)
Return the current session summary text.
↳ set_cross_session(self, text: str)
Hydrate the cross-session summary from external storage.
↳ _chunk_to_summary(self, chunk: Chunk, priority: float)
Convert a single chunk to a summary string. Higher-priority chunks retain more detail.
↳ _fallback_summarize(text: str, max_chars: int = 1500)
Fallback summarizer: truncate intelligently without an LLM. Keeps the first 40 % and last 20 %, with structural markers.
class ContextAssembler
Assemble the final context payload for an LLM request. Layout (in order, each section included only if it fits): 1. System prompt (always, highest priority) 2. Cross-session memory (Level 3 summary) 3. Session summary (Level 2 summary) 4. High-priority recent chunks (Level 1, expand
↳ __init__(self, token_budget: int = 128000, system_prompt_tokens: int = 2000, reserve_ratio: float = 0.25)
↳ assemble(self, level0_messages: list[dict[str, Any]], level1_summaries: list[SummaryLevel], level2_session: SummaryLevel, level3_cross: SummaryLevel, chunk_priorities: list[ChunkPriority], system_prompt: str | None = None)
Build the final message list respecting the token budget. Args: level0_messages: raw recent messages (newest at end) level1_summaries: Level-1 chunk summaries level2_session: Level-2 sess
↳ set_budget(self, token_budget: int)
Update the token budget (thread-safe).
class RelevanceScorer
Score how relevant historical messages/chunks are to a current query. Uses fast heuristics (no embeddings): * keyword_overlap — shared words between query and target * file_path_match — shared file paths * topic_similarity — topic label overlap * recency_bonus — prefer recent conte
↳ __init__(self, keyword_weight: float = 0.35, path_weight: float = 0.3, topic_weight: float = 0.2, recency_weight: float = 0.15)
↳ score_messages(self, query: str, messages: list[dict[str, Any]])
Score each message's relevance to *query*. Returns: List of (message_index, relevance_score) sorted by score desc.
↳ score_chunks(self, query: str, chunks: list[Chunk])
Score each chunk's relevance to *query*. Returns: List of (chunk_index, relevance_score) sorted by score desc.
↳ _keyword_overlap(query_words: set[str], target_words: set[str])
Jaccard-ish word overlap.
↳ _path_overlap(query_paths: set[str], target_paths: set[str])
File path overlap: exact match or shared directory.
↳ _topic_similarity(query_text: str, target_text: str)
Simple topic similarity based on shared n-grams.
class SmartContextEngine
Intelligent context manager — drop-in replacement for compaction.py. Replaces ``maybe_compact()``, ``compact_messages()``, ``find_split_point()``, ``estimate_tokens()``, and ``snip_old_tool_results()`` with a system that understands *semantics*, *priority*, and *hierarchy*. Usage (drop-in)::
↳ __init__(self, llm_callback: 'callable | None' = None, max_chunk_tokens: int = 800, token_budget_ratio: float = 0.65, weights: dict[str, float] | None = None)
↳ maybe_compact(self, state, config: dict)
Check if the context window is getting full and compress if needed. Compatible signature with ``compaction.maybe_compact()``. 1. Runs semantic analysis on messages. 2. Builds priority-ranked chunks.
↳ compact_messages(self, messages: list[dict[str, Any]], config: dict, focus: str = '')
Compress old messages into a smart summary. Compatible signature with ``compaction.compact_messages()``. Args: messages: full message list config: agent config dict (must contain "model")
↳ manual_compact(self, state, config: dict, focus: str = '')
User-triggered compaction via ``/compact``. Compatible signature with ``compaction.manual_compact()``. Returns: (success, info_message)
↳ chunk_messages(self, messages: list[dict[str, Any]])
Run semantic chunking on messages. Returns: List of :class:`Chunk`.
↳ rank_chunks(self, chunks: list[Chunk])
Rank chunks by priority. Returns: List of :class:`ChunkPriority`.
↳ build_hierarchy(self, chunks: list[Chunk], priorities: list[ChunkPriority] | None = None, config: dict | None = None)
Build the Level-1 summary hierarchy. Level-2 and Level-3 summaries are also produced if an LLM callback was configured. Returns: List of Level-1 :class:`SummaryLevel` objects.
↳ assemble_context(self, messages: list[dict[str, Any]], chunks: list[Chunk], priorities: list[ChunkPriority], level1_summaries: list[SummaryLevel], system_prompt: str | None = None, model: str = '')
Assemble the final context within the token budget. Args: messages: raw recent messages chunks: semantic chunks priorities: chunk priorities level1_summaries: Level-1 summaries sy
↳ score_relevance(self, query: str, messages: list[dict[str, Any]])
Score message relevance against a query. Returns: List of (index, score) sorted by score descending.
↳ get_stats(self)
Return compaction statistics.
↳ estimate_tokens(self, messages: list[dict[str, Any]])
Backward-compatible token estimator. Same algorithm as ``compaction.estimate_tokens()`` (chars / 2.8).
↳ get_context_limit(self, model: str)
Backward-compatible context limit lookup.
↳ snip_old_tool_results(self, messages: list[dict[str, Any]], max_chars: int = 2000, preserve_last_n_turns: int = 6)
Backward-compatible tool-result snipper. Same behavior as ``compaction.snip_old_tool_results()``. Operates on a copy — does NOT mutate in place.
↳ find_split_point(self, messages: list[dict[str, Any]], keep_ratio: float = 0.3)
Backward-compatible split point finder. Uses semantic priority rather than raw token counts: walks backwards accumulating tokens and returns the index where the recent portion reaches ~*keep_ratio* o
↳ _wrap_llm_callback(self, text: str, focus: str = '')
Adapt our internal llm_callback to the HierarchicalSummarizer interface.
↳ _restore_plan_context(config: dict)
Restore plan context after compaction (from compaction.py).
Functions (18)
def _extract_text_content(message: dict[str, Any])
Extract plain text from a message dict (handles string or list content).
def _count_tokens_single(message: dict[str, Any])
Estimate tokens for a single message.
def _estimate_tokens(messages: list[dict[str, Any]])
Estimate total tokens for a list of messages (with 10% safety buffer).
def _extract_file_paths(text: str)
Extract potential file paths from text using simple heuristics.
def _detect_provider(model: str)
Detect provider name from model string.
def _get_context_limit(model: str)
Look up context window size for a model.
def _extract_tool_name(message: dict[str, Any])
Extract tool name from a tool-related message.
def _compute_boundary_score(prev_msg: dict[str, Any] | None, curr_msg: dict[str, Any], next_msg: dict[str, Any] | None)
Compute a boundary score for placing a split *before* curr_msg. Returns a float in [0, 1]. Higher = stronger boundary.
def _infer_topic(messages: list[dict[str, Any]])
Infer a topic label from a list of messages.
def _patch_summarizer_accessors()
Monkey-patch convenience accessors onto HierarchicalSummarizer.
def _get_default_engine()
Return (creating if needed) the module-level default engine.
def estimate_tokens(messages: list[dict[str, Any]], **kwargs)
Top-level token estimator (backward-compatible).
def get_context_limit(model: str)
Top-level context limit lookup (backward-compatible).
def snip_old_tool_results(messages: list[dict[str, Any]], max_chars: int = 2000, preserve_last_n_turns: int = 6)
Top-level tool-result snipper (backward-compatible, non-mutating).
def find_split_point(messages: list[dict[str, Any]], keep_ratio: float = 0.3, **kwargs)
Top-level split-point finder (backward-compatible).
def compact_messages(messages: list[dict[str, Any]], config: dict, focus: str = '')
Top-level compact function (backward-compatible).
def maybe_compact(state, config: dict)
Top-level entry point (backward-compatible with compaction.maybe_compact).
def manual_compact(state, config: dict, focus: str = '')
Top-level manual compact (backward-compatible).
Imports
__future__collections dataclasses re threading time typing
▶New folder/context_engine/test_smart_context.py 1020 LOC
Exhaustive tests for the Smart Context Engine. Run with: python -m pytest test_smart_context.py -v python test_smart_context.py # unittest runner
Classes (14)
class TestTextExtraction
Test _extract_text_content and related helpers.
↳ test_string_content(self)
↳ test_list_content_text_block(self)
↳ test_tool_calls_content(self)
↳ test_empty_content(self)
↳ test_none_content(self)
class TestTokenEstimation
Test token estimation helpers.
↳ test_empty_messages(self)
↳ test_single_message(self)
↳ test_estimate_tokens_with_framing(self)
↳ test_tool_calls_counted(self)
class TestProviderDetection
Test provider/model detection.
↳ test_kimi_models(self)
↳ test_anthropic_models(self)
↳ test_openai_models(self)
↳ test_ollama_models(self)
↳ test_default_fallback(self)
↳ test_context_limits(self)
class TestFilePathExtraction
Test file path extraction from text.
↳ test_simple_path(self)
↳ test_absolute_path(self)
↳ test_home_path(self)
↳ test_no_paths(self)
↳ test_multiple_paths(self)
class TestBoundaryScoring
Test boundary score computation.
↳ test_role_transition_assistant_to_tool(self)
↳ test_decision_marker_boundary(self)
↳ test_no_boundary(self)
↳ test_explicit_separator(self)
class TestTopicInference
Test topic inference for chunks.
↳ test_tool_topic(self)
↳ test_file_topic(self)
↳ test_marker_topic(self)
↳ test_general_topic(self)
class TestSemanticChunker
Test SemanticChunker class.
↳ test_empty_messages(self)
↳ test_short_conversation_single_chunk(self)
↳ test_chunk_indices_continuous(self)
↳ test_chunk_has_topic(self)
↳ test_chunk_counts_decisions(self)
↳ test_chunk_file_paths(self)
↳ test_chunks_have_token_estimates(self)
↳ test_tool_heavy_conversation(self)
↳ test_chunk_message_types(self)
class TestPriorityRanker
Test PriorityRanker class.
↳ test_empty_chunks(self)
↳ test_all_chunks_scored(self)
↳ test_scores_in_range(self)
↳ test_recency_newer_higher(self)
More recent chunks should generally score higher.
↳ test_error_chunks_ranked_high(self)
Chunks with errors should have good structural scores.
↳ test_top_chunks_returns_sorted(self)
↳ test_weights_are_normalized(self)
Custom weights should be normalized to sum to 1.0.
↳ test_reference_scoring(self)
Chunks with file path overlap should have reference connections.
class TestHierarchicalSummarizer
Test HierarchicalSummarizer class.
↳ test_summarize_chunks_basic(self)
↳ test_chunk_to_summary_has_topic(self)
↳ test_fallback_summarize(self)
↳ test_fallback_summarize_short_text(self)
↳ test_session_summary_without_llm(self)
↳ test_cross_session_update(self)
↳ test_cross_session_persistence(self)
↳ test_set_cross_session(self)
↳ test_level1_priority_scaling(self)
High-priority chunks should have more detail in their summary.
↳ test_with_mock_llm(self)
Test summarizer with a mock LLM callback.
class TestContextAssembler
Test ContextAssembler class.
↳ test_empty_inputs(self)
↳ test_level0_only(self)
↳ test_respects_budget(self)
↳ test_level3_included_when_fits(self)
↳ test_level3_excluded_when_too_large(self)
↳ test_system_prompt_reservation(self)
class TestRelevanceScorer
Test RelevanceScorer class.
↳ test_empty_query(self)
↳ test_keyword_match(self)
↳ test_file_path_match(self)
↳ test_chunk_relevance(self)
↳ test_error_query_boosts_error_chunks(self)
↳ test_sorted_descending(self)
class MockState
Mock agent state with messages.
↳ __init__(self, messages)
class TestSmartContextEngine
Test SmartContextEngine (drop-in replacement).
↳ test_init_defaults(self)
↳ test_init_with_callback(self)
↳ test_chunk_messages(self)
↳ test_rank_chunks(self)
↳ test_build_hierarchy(self)
↳ test_estimate_tokens_compat(self)
↳ test_get_context_limit_compat(self)
↳ test_snip_old_tool_results(self)
↳ test_find_split_point(self)
↳ test_score_relevance(self)
↳ test_get_stats_initial(self)
↳ test_compact_messages(self)
↳ test_compact_messages_short(self)
Short conversations (< 4 msgs) should not be compacted.
↳ test_maybe_compact_under_threshold(self)
Messages under threshold should not be compacted.
↳ test_maybe_compact_over_threshold(self)
Messages over threshold should be compacted.
↳ test_manual_compact(self)
↳ test_manual_compact_not_enough_messages(self)
↳ test_assemble_context(self)
↳ test_with_mock_llm_callback(self)
↳ test_thread_safety(self)
Engine should be safe to call from multiple threads.
class TestEndToEnd
End-to-end workflow tests.
↳ test_full_pipeline(self)
Run the complete pipeline from messages to assembled context.
↳ test_error_pipeline(self)
Pipeline with error-heavy conversation.
↳ test_relevance_drives_context(self)
Relevance scoring should surface relevant messages.
↳ test_compaction_reduces_tokens(self)
Compaction should generally reduce token count.
↳ test_top_level_functions(self)
Test top-level backward-compatible functions.
Functions (5)
def msg(role: str, content: str, **extras)
Build a message dict.
def make_conversation()
Return a realistic multi-turn conversation.
def make_error_conversation()
Return a conversation with errors for priority testing.
def make_short_conversation()
Return a very short conversation (edge case).
def make_tool_heavy_conversation()
Return a conversation dominated by tool calls.
Imports
__future__pathlib smart_context sys threading time unittest
▶New folder/deploy/build_all.py 628 LOC
Classes (5)
class Builder
Base class para builders.
↳ __init__(self, project_root: Path, version: str)
↳ build(self)
↳ get_artifacts(self)
class DockerBuilder
Builder para Docker images.
↳ build(self)
class WindowsBuilder
Builder para Windows usando PyInstaller.
↳ build(self)
class LinuxBuilder
Builder para Linux (AppImage + tarball).
↳ build(self)
class MacBuilder
Builder para macOS (.app bundle).
↳ build(self)
Functions (12)
def log(msg: str, level: str = 'INFO')
Log con formato y colores.
def print_banner()
Imprime banner de inicio.
def ensure_dirs()
Crea directorios necesarios.
def find_project_root()
Encuentra la raiz del proyecto.
def detect_platform()
Detecta la plataforma actual.
def run_command(cmd: list, cwd: Path = None, timeout: int = 600)
Ejecuta un comando y retorna exito/fracaso.
def sha256_file(path: Path)
Calcula SHA256 de un archivo.
def file_size(path: Path)
Retorna tamano legible.
def generate_checksums(artifacts: list)
Genera SHA256SUMS.txt para todos los artefactos.
def generate_release_notes(version: str, artifacts: list, platform_info: dict)
Genera release notes automaticas.
def create_github_release(version: str, notes_path: Path, dry_run: bool = True)
Crea un release en GitHub (requiere GITHUB_TOKEN).
def main()
Imports
argparse datetime hashlib json os pathlib platform shutil subprocess sys
▶New folder/deploy/build_windows.py 615 LOC
Functions (11)
def log(msg: str, level: str = 'INFO')
Log con formato.
def ensure_dirs()
Crea directorios necesarios.
def find_project_root()
Encuentra la raiz del proyecto (donde esta dulus.py).
def generate_spec_file(project_root: Path, onefile: bool = False)
Genera el archivo .spec para PyInstaller.
def generate_version_file(project_root: Path)
Genera version.txt para el ejecutable Windows.
def build_executable(project_root: Path, spec_file: Path, onefile: bool)
Ejecuta PyInstaller para construir el ejecutable.
def create_nsis_installer(project_root: Path)
Crea un installer NSIS para Windows.
def create_zip_portable(project_root: Path)
Crea un paquete ZIP portable como alternativa al NSIS installer.
def sign_executable(project_root: Path)
Firma el ejecutable con un certificado (requiere certificado instalado).
def generate_checksums(project_root: Path)
Genera checksums SHA256 de todos los artefactos.
def main()
Imports
argparse datetime hashlib os pathlib re shutil subprocess sys
▶New folder/deploy/updater.py 665 LOC
Functions (27)
def log(msg: str, level: str = 'INFO')
Log con formato.
def log_error_exit(msg: str, code: int = 1)
def http_get(url: str, headers: dict = None, timeout: int = 30)
GET request que retorna JSON.
def download_file(url: str, dest: Path, progress: bool = True)
Descarga un archivo con barra de progreso simple.
def parse_version(v: str)
Parsea version '1.2.3' -> (1, 2, 3). Soporta pre-release.
def version_greater(new: str, current: str)
Compara si new > current.
def is_prerelease(version: str)
Detecta si es pre-release (alpha, beta, rc).
def channel_matches(version: str, channel: str)
Verifica si la version corresponde al canal seleccionado.
def get_latest_release()
Obtiene la ultima release desde GitHub.
def get_releases(limit: int = 10)
Obtiene lista de releases recientes.
def find_appropriate_asset(assets: list)
Encuentra el asset correcto para la plataforma actual.
def check_for_update(channel: str = None)
Chequea si hay una actualizacion disponible.
def backup_current()
Crea backup de la instalacion actual.
def apply_update(update_info: dict)
Descarga y aplica la actualizacion.
def apply_zip_update(zip_path: Path, install_dir: Path)
Aplica update desde ZIP.
def apply_tarball_update(tar_path: Path, install_dir: Path)
Aplica update desde tarball.
def apply_appimage_update(appimage_path: Path, install_dir: Path)
Aplica update de AppImage.
def _copy_update_files(src_dir: Path, dest_dir: Path)
Copia archivos del extract al destino.
def verify_checksum(file_path: Path, checksum_path: Path)
Verifica SHA256 checksum.
def rollback(backup_path: Path, install_dir: Path)
Restaura desde backup si el update falla.
def pypi_check()
Chequea version en PyPI.
def pypi_update()
Actualiza via pip desde PyPI.
def cmd_check(args)
Comando: check.
def cmd_update(args)
Comando: update.
def cmd_rollback(args)
Comando: rollback.
def cmd_version(args)
Comando: version.
def main()
Imports
hashlib json os pathlib platform shutil ssl subprocess sys tarfile tempfile urllib.error urllib.parse urllib.request zipfile
▶New folder/devops/scripts/health_check.py 768 LOC
Dulus RTK — Health Check Script ================================ Verificación completa del estado del sistema Dulus RTK. Uso: python scripts/health_check.py # Tabla en terminal python scripts/health_check.py --json # Output JSON python scripts/health_check.py --save # Guardar reporte a archivo Verifica: - Todos los imports del proyecto - API keys configuradas para cada provider - Directorios necesarios (~/.dulus/) - Registro de herramientas - Tamaño de
Functions (18)
def _color(status: str, text: str)
def _icon(status: str)
def _header(text: str)
def _row(label: str, value: str, status: str = 'ok')
def check_imports()
Verifica que todos los módulos principales se puedan importar.
def check_api_keys()
Verifica que las API keys de providers estén configuradas.
def check_directories()
Verifica que los directorios necesarios existan.
def check_files()
Verifica que los archivos críticos del proyecto existan.
def check_tools()
Verifica que las herramientas se registren correctamente.
def check_sessions()
Revisa tamaño de sesiones almacenadas.
def check_system()
Verifica recursos del sistema.
def check_version()
Lee la versión actual del proyecto.
def _human_readable_size(size_bytes: int)
Convierte bytes a formato humano legible.
def print_table(report: dict[str, Any])
Imprime el reporte en formato tabla.
def print_json(report: dict[str, Any])
Imprime el reporte en formato JSON.
def build_report()
Construye el reporte completo de health check.
def flatten_report(report: dict[str, Any])
Aplana la estructura nested para acceso más sencillo.
def main()
Imports
__future__argparse datetime importlib json os pathlib platform re subprocess sys typing
▶New folder/devops/scripts/lint.py 217 LOC
Dulus RTK — Lint Script ======================== Script de linting local para el proyecto Dulus RTK. Uso: python scripts/lint.py # Verifica linting y formato (dry-run) python scripts/lint.py --fix # Auto-corregir problemas detectados Requiere: pip install ruff black Salida: - Reporte con formato amigable en terminal - Código de salida 0 si todo OK, 1 si hay errores
Functions (8)
def _cmd(tool: str, *args)
Retorna el comando apropiado para ejecutar una herramienta.
def _run(cmd: list[str], description: str)
Ejecuta un comando y retorna (éxito, stdout+stderr).
def _header(text: str)
def _success(text: str)
def _warning(text: str)
def _error(text: str)
def check_command_available(cmd: str)
Verifica si un comando está disponible (en PATH o como modulo Python).
def main()
Imports
__future__argparse shutil subprocess sys typing
▶New folder/devops/scripts/test.py 295 LOC
Dulus RTK — Test Script ======================== Script de testing local con múltiples modos de ejecución. Uso: python scripts/test.py # Tests unitarios con coverage python scripts/test.py --fast # Solo unitarios, sin coverage (rápido) python scripts/test.py --full # Unitarios + E2E con coverage python scripts/test.py --failed # Re-ejecutar tests que fallaron python scripts/test.py -k expr # Filtrar tests por keyword Requiere: pip install pytest pytest-co
Functions (9)
def _header(text: str)
def _success(text: str)
def _warning(text: str)
def _error(text: str)
def _info(text: str)
def _run(cmd: list[str])
Ejecuta un comando, retorna (éxito, output).
def check_pytest()
Verifica que pytest esté instalado.
def run_tests()
Ejecuta los tests según los parámetros dados.
def main()
Imports
__future__argparse pathlib subprocess sys typing webbrowser
▶New folder/devops/scripts/version.py 604 LOC
Dulus RTK — Version Management Script ===================================== Gestión de versiones semánticas (semver) para Dulus RTK. Uso: python scripts/version.py # Muestra versión actual python scripts/version.py bump patch # 1.0.0 → 1.0.1 python scripts/version.py bump minor # 1.0.0 → 1.1.0 python scripts/version.py bump major # 1.0.0 → 2.0.0 python scripts/version.py bump build # 1.0.0 → 1.0.0+1 python scripts/version.py set 2.0.0 # Establec
Classes (1)
class SemVer
Representa una versión semántica.
↳ parse(cls, s: str)
Parsea una string de versión en SemVer.
↳ bump(self, level: str)
Incrementa la versión en el nivel indicado.
↳ __str__(self)
↳ tag_name(self)
Functions (20)
def _header(text: str)
def _success(text: str)
def _warning(text: str)
def _error(text: str)
def _info(text: str)
def read_dulus_version()
Lee la versión actual de dulus.py (variable VERSION).
def read_pyproject_version()
Lee la versión actual de pyproject.toml.
def update_dulus_py(version: SemVer)
Actualiza la versión en dulus.py.
def update_pyproject(version: SemVer)
Actualiza la versión en pyproject.toml.
def update_docs(version: SemVer)
Actualiza referencias a versión en docs/ si existen.
def generate_changelog_entry(version: SemVer, prev_version: str | None)
Genera una nueva entrada de CHANGELOG con los commits recientes.
def update_changelog(version: SemVer, prev_version: str | None)
Añade nueva entrada al CHANGELOG.md.
def create_git_tag(version: SemVer, message: str | None = None)
Crea un tag git anotado para la versión.
def push_git_tag(version: SemVer, dry_run: bool = False)
Empuja el tag al remoto.
def cmd_show()
Muestra la versión actual.
def cmd_bump(level: str)
Incrementa la versión.
def cmd_set(version_str: str)
Establece la versión explícitamente.
def cmd_tag()
Crea (y opcionalmente empuja) el git tag.
def cmd_changelog()
Muestra la última entrada del CHANGELOG.
def main()
Imports
__future__argparse datetime pathlib re subprocess sys typing
▶New folder/memory_v2/__init__.py 98 LOC
Memory V2 — Vector Search + Knowledge Graph + Session Linking Dulus RTK's advanced memory subsystem with: - TF-IDF vector search (numpy, zero external deps) - Knowledge graph with entity/topic extraction - Cross-session memory linking and threads - Hybrid search (keyword + semantic + graph) Quick Start: >>> from memory_v2 import MemoryIndex >>> index = MemoryIndex(Path("~/.dulus/memory_v2")) >>> index.add_memory("setup", "Docker container setup guide")
▶New folder/memory_v2/index.py 546 LOC
MemoryIndex — Master coordinator for the Memory V2 system. Orchestrates VectorMemoryStore, KnowledgeGraph, SessionMemoryLinker, and UnifiedMemoryQuery into a single unified interface. This is the main entry point for the rest of the Dulus system. Example: >>> from memory_v2.index import MemoryIndex >>> index = MemoryIndex(Path("~/.dulus/memory_v2")) >>> index.add_memory("setup", "Docker setup guide for production") >>> results = index.sear
Classes (1)
class MemoryIndex
Master index coordinating all memory v2 subsystems. Provides a unified API for storing, searching, and navigating memories. All persistence is JSON-based. Thread-safe. Attributes: base_dir: Root directory for all persistence. vector_store: TF-IDF vector storage. knowledge_graph: Semant
↳ __init__(self, base_dir: Path, auto_save: bool = True)
Initialize the memory index. Args: base_dir: Root directory for persistence (subsystems create their own subdirectories). auto_save: Whether to auto-save on mutations.
↳ save(self)
Explicitly save all subsystems.
↳ load(self)
Explicitly load all subsystems.
↳ add_memory(self, memory_id: str, content: str, scope: str = 'user', tags: Optional[List[str]] = None, gold: bool = False, source_path: str = '')
Add or update a memory in all subsystems. This is the main method for storing a memory. It: 1. Creates/updates the MemoryRecord in the vector store 2. Adds the memory node and extracts entities/topic
↳ add_memory_from_markdown(self, memory_id: str, markdown_text: str, scope: str = 'user', source_path: str = '')
Add a memory from a frontmatter + markdown string. Parses frontmatter for tags, gold flag, etc. Args: memory_id: Unique identifier. markdown_text: Text with optional YAML frontmatter. sc
↳ get_memory(self, memory_id: str)
Get a single memory by ID. Args: memory_id: Memory identifier. Returns: MemoryRecord or None.
↳ remove_memory(self, memory_id: str)
Remove a memory from all subsystems. Args: memory_id: Memory identifier. Returns: True if removed.
↳ list_memories(self, scope: Optional[str] = None)
List all memories, optionally filtered by scope. Args: scope: "user", "project", or None for all. Returns: List of MemoryRecord objects.
↳ pin_memory(self, memory_id: str)
Pin (gold) a memory so it auto-loads at startup. Args: memory_id: Memory to pin. Returns: True if pinned.
↳ unpin_memory(self, memory_id: str)
Unpin (remove gold) a memory. Args: memory_id: Memory to unpin. Returns: True if unpinned.
↳ search(self, query: str, top_k: int = 5, scope: Optional[str] = None, mode: str = 'hybrid')
Search memories using the specified mode. Args: query: Search query. top_k: Maximum results. scope: Optional scope filter. mode: "keyword", "semantic", "graph", or "hybrid" (default).
↳ get_related(self, memory_id: str, top_k: int = 5)
Get memories related to a specific memory. Args: memory_id: Reference memory ID. top_k: Maximum results. Returns: List of SearchResult.
↳ discover(self, query: str)
Knowledge discovery: "What do we know about X?" Args: query: Discovery query. Returns: Rich dict with memories, topics, entities, summary.
↳ suggest_for_new_session(self, session_id: str, system_prompt: str = '', user_messages: Sequence[str] = (), top_k: int = 5)
Suggest relevant memories at session start. Args: session_id: New session identifier. system_prompt: Session system prompt. user_messages: Initial user messages. top_k: Maximum sugges
↳ register_session(self, session_id: str, label: str, system_prompt: str = '', user_messages: Sequence[str] = (), parent_session: Optional[str] = None)
Register a new session and get/create its thread. Args: session_id: Unique session identifier. label: Human-readable session label. system_prompt: System prompt text. user_messages: I
↳ link_memory_to_session(self, memory_id: str, session_id: str)
Associate a memory with the session that created it. Args: memory_id: Memory ID. session_id: Session ID.
↳ get_session_thread(self, session_id: str)
Get the thread for a session. Args: session_id: Session identifier. Returns: MemoryThread or None.
↳ list_threads(self)
List all memory threads. Returns: List of MemoryThread objects.
↳ get_graph_stats(self)
Get statistics about the knowledge graph. Returns: Dict with node/edge counts, top topics, entities.
↳ get_memory_graph(self, memory_id: str)
Get the graph neighborhood around a memory. Args: memory_id: Memory ID. Returns: Dict with "nodes" and "edges" in the neighborhood.
↳ export_all(self)
Export entire memory index as a JSON-serializable dict. Returns: Complete export of all subsystems.
↳ stats(self)
Get comprehensive statistics. Returns: Dict with counts, sizes, and health metrics.
Imports
__future__datetime json pathlib re threading typing
▶New folder/memory_v2/knowledge_graph.py 1006 LOC
Knowledge Graph — Semantic graph of memories, topics, and entities. Nodes: - memory: a stored memory entry - topic: an auto-extracted topic/theme - entity: a file path, URL, email, or named entity - session: a conversation session Edges: - related_to: semantic similarity between memories - mentions: memory mentions an entity - has_topic: memory belongs to a topic - depends_on: memory depends on another - part_of: entity is part of another entity - follow
Classes (5)
class NodeType
Types of nodes in the knowledge graph.
class EdgeType
Types of edges (relationships) in the knowledge graph.
class Node
A node in the knowledge graph. Attributes: id: Unique node identifier. type: Node type (memory, topic, entity, session). label: Human-readable label. metadata: Optional key-value metadata. created_at: ISO timestamp.
↳ to_dict(self)
↳ from_dict(cls, d: dict)
class Edge
An edge (relationship) in the knowledge graph. Attributes: source: Source node ID. target: Target node ID. type: Edge type. weight: Relationship strength [0, 1]. metadata: Optional key-value metadata. created_at: ISO timestamp.
↳ id(self)
Canonical edge identifier for dedup.
↳ to_dict(self)
↳ from_dict(cls, d: dict)
class KnowledgeGraph
In-memory knowledge graph with adjacency-list storage and JSON persistence. Nodes represent memories, topics, entities, and sessions. Edges represent semantic relationships between them. Supports graph traversal, neighborhood queries, path finding, and knowledge discovery. Example: >>&g
↳ __init__(self, persist_dir: Path, auto_save: bool = True)
Initialize the knowledge graph. Args: persist_dir: Directory for JSON persistence. auto_save: Auto-save on every mutation.
↳ _nodes_path(self)
↳ _edges_path(self)
↳ _save(self)
Persist graph to JSON.
↳ _load(self)
Load graph from JSON.
↳ _rebuild_adjacency(self)
Rebuild adjacency lists from edges.
↳ _make_edge_id(self, source: str, edge_type: str, target: str)
↳ _add_edge_internal(self, source: str, target: str, edge_type: str, weight: float = 1.0, metadata: Optional[Dict[str, Any]] = None)
Add an edge (assumes nodes exist, no lock).
↳ add_node(self, node: Node)
Add a node to the graph. Args: node: Node to add.
↳ add_memory_node(self, memory_id: str, content: str, scope: str = 'user', tags: Optional[List[str]] = None)
Add a memory node and auto-extract connected entities/topics. This is the main entry point for indexing a memory into the graph. It creates the memory node, extracts entities, topics, and key phrases
↳ add_session_node(self, session_id: str, label: str, metadata: Optional[Dict[str, Any]] = None)
Add a session node to the graph. Args: session_id: Unique session identifier. label: Human-readable session label. metadata: Optional session metadata.
↳ link_sessions(self, from_session: str, to_session: str, weight: float = 0.5)
Create a follows_from edge between two sessions. Args: from_session: Source session ID (without sess: prefix). to_session: Target session ID (without sess: prefix). weight: Edge weight.
↳ link_memory_to_session(self, memory_id: str, session_id: str)
Link a memory to the session that created it. Args: memory_id: Memory ID (without mem: prefix). session_id: Session ID (without sess: prefix).
↳ link_similar_memories(self, memory_id1: str, memory_id2: str, similarity: float)
Create a bidirectional similar_to edge between two memories. Args: memory_id1: First memory ID (without mem: prefix). memory_id2: Second memory ID (without mem: prefix). similarity: Cosin
↳ remove_node(self, node_id: str)
Remove a node and all its edges. Args: node_id: Full node ID (e.g., "mem:setup"). Returns: True if removed.
↳ remove_memory_node(self, memory_id: str)
Remove a memory node and clean up orphan entities/topics. Args: memory_id: Memory ID (without mem: prefix). Returns: True if removed.
↳ get_node(self, node_id: str)
Get a node by ID. Args: node_id: Full node ID. Returns: Node or None.
↳ get_memory_node(self, memory_id: str)
Get a memory node by memory ID. Args: memory_id: Memory ID (without mem: prefix). Returns: Node or None.
↳ _sanitize_id(text: str)
Sanitize text for use in node IDs.
↳ neighbors(self, node_id: str, edge_type: Optional[str] = None, direction: str = 'outgoing')
Get neighboring nodes. Args: node_id: Full node ID. edge_type: Filter by edge type (or all types if None). direction: "outgoing", "incoming", or "both". Returns: List of (Node, edge_
↳ memories_for_entity(self, entity_label: str)
Find all memory nodes that mention a specific entity. Args: entity_label: The entity text (e.g., "app.py"). Returns: List of memory Nodes.
↳ memories_for_topic(self, topic: str)
Find all memory nodes with a specific topic. Args: topic: Topic name (e.g., "docker"). Returns: List of memory Nodes.
↳ discover(self, query: str, depth: int = 2)
Knowledge discovery: "What do we know about X?" Starts from nodes matching the query and traverses the graph to find related memories, topics, and entities. Args: query: Search query (topic, ent
↳ related_memories(self, memory_id: str, max_hops: int = 2)
Find memories related to a given memory via graph traversal. Traverses the graph from a memory node, following all edge types, and returns other memory nodes ranked by cumulative path weight. Args:
↳ get_topics_summary(self)
Get counts of memories per topic. Returns: Dict mapping topic label to memory count.
↳ get_entity_summary(self, entity_type: Optional[str] = None, top_n: int = 20)
Get most frequently mentioned entities. Args: entity_type: Filter by entity type (file_path, url, email, etc.). top_n: Maximum number to return. Returns: List of (entity_label, mention_c
↳ stats(self)
Graph statistics. Returns: Dict with node counts, edge counts, top topics, etc.
↳ clear(self)
Remove all nodes and edges.
↳ __len__(self)
↳ __iter__(self)
Functions (4)
def _tokenize_simple(text: str)
Simple tokenization for topic extraction.
def extract_entities(text: str)
Extract typed entities from memory text. Args: text: Memory content to analyze. Returns: Dict mapping entity type to list of unique extracted values.
def extract_topics(text: str)
Extract topic scores from memory text. Scores each predefined topic by counting matching keywords. Args: text: Memory content to analyze. Returns: Dict mapping topic name to score (>0 means matched).
def extract_key_phrases(text: str, top_n: int = 5)
Extract key phrases (bigrams and trigrams) from text. Uses simple frequency-based scoring. Args: text: Input text. top_n: Number of top phrases to return. Returns: List of key phrases sorted by importance.
Imports
__future__collections dataclasses datetime enum json numpy pathlib re threading typing
▶New folder/memory_v2/query_engine.py 579 LOC
Unified Memory Query Engine — Hybrid search across all memory dimensions. Combines four search strategies into a unified interface: 1. Keyword search — exact token matching 2. Semantic search — TF-IDF vector cosine similarity 3. Graph search — navigation of knowledge graph relationships 4. Hybrid search — weighted combination of all three All searches return consistently scored results for easy ranking.
Classes (2)
class SearchResult
A single search result with unified scoring. Attributes: record: The memory record. keyword_score: Score from keyword search [0, 1]. semantic_score: Score from vector similarity [0, 1]. graph_score: Score from graph navigation [0, 1]. final_score: Combined weighted score. so
↳ __init__(self, record: MemoryRecord, keyword_score: float = 0.0, semantic_score: float = 0.0, graph_score: float = 0.0, source: str = '')
↳ _compute_final(self)
Compute weighted final score.
↳ with_weights(self, kw_weight: float = 0.25, sem_weight: float = 0.5, graph_weight: float = 0.25)
Return a copy with custom weights. Args: kw_weight: Keyword search weight. sem_weight: Semantic search weight. graph_weight: Graph search weight. Returns: New SearchResult with recom
↳ to_dict(self)
↳ __repr__(self)
↳ __lt__(self, other: 'SearchResult')
class UnifiedMemoryQuery
Unified query engine for searching memories across all dimensions. Provides keyword, semantic, graph, and hybrid search methods. All methods return SearchResult objects with unified scoring. Example: >>> query = UnifiedMemoryQuery(vector_store, knowledge_graph) >>> result
↳ __init__(self, vector_store: VectorMemoryStore, knowledge_graph: KnowledgeGraph)
Initialize the query engine. Args: vector_store: Vector memory store. knowledge_graph: Knowledge graph.
↳ keyword(self, query: str, top_k: int = 10, scope: Optional[str] = None)
Keyword-based search — exact token matching. Fast, good for precise matches. Scores by Jaccard-like overlap between query tokens and memory tokens. Args: query: Search query. top_k: Maximum
↳ semantic(self, query: str, top_k: int = 10, scope: Optional[str] = None, min_score: float = 0.0)
Semantic search — TF-IDF vector cosine similarity. Good for finding conceptually related memories even when keywords don't match exactly. Args: query: Search query. top_k: Maximum results.
↳ graph(self, query: str, top_k: int = 10, max_hops: int = 2)
Graph-based search — navigate knowledge graph relationships. Starts from nodes matching the query and traverses the graph to find connected memories. Good for discovery and contextual recall. Args:
↳ hybrid(self, query: str, top_k: int = 10, scope: Optional[str] = None, min_score: float = 0.05, weights: Optional[Tuple[float, float, float]] = None)
Hybrid search — combines keyword, semantic, and graph search. Runs all three search strategies, merges results, and re-ranks by weighted combination. This is the recommended default search method. A
↳ find_by_entity(self, entity: str, top_k: int = 10)
Find memories that mention a specific entity. Args: entity: Entity label (e.g., "app.py" or a URL). top_k: Maximum results. Returns: List of SearchResult.
↳ find_by_topic(self, topic: str, top_k: int = 10)
Find memories about a specific topic. Args: topic: Topic name (e.g., "docker", "python"). top_k: Maximum results. Returns: List of SearchResult.
↳ find_similar(self, memory_id: str, top_k: int = 5)
Find memories similar to a specific memory. Combines vector similarity with graph adjacency. Args: memory_id: Memory ID to find similar memories for. top_k: Maximum results. Returns: Li
↳ discover_knowledge(self, query: str)
Knowledge discovery query: "What do we know about X?" Returns a rich response with memories, topics, entities, and relationships related to the query. Args: query: Discovery query. Returns:
↳ explain_result(self, result: SearchResult, query: str)
Generate a human-readable explanation of why a result matched. Args: result: SearchResult to explain. query: Original query. Returns: Human-readable explanation string.
Imports
__future__threading typing
▶New folder/memory_v2/session_linker.py 574 LOC
Session Memory Linker — Cross-session memory continuity. Detects when a new session continues work from previous sessions, suggests relevant memories from past sessions, and builds "memory threads" — continuous lines of work across sessions. Uses the knowledge graph and vector store to find connections between the current session context and historical sessions.
Classes (2)
class MemoryThread
A continuous line of work spanning multiple sessions. Attributes: id: Thread identifier. name: Human-readable thread name (auto-generated from key topic). session_ids: Ordered list of session IDs in this thread. memory_ids: Memory IDs associated with this thread. key_topics: Top
↳ to_dict(self)
↳ from_dict(cls, d: dict)
class SessionMemoryLinker
Links memories across sessions for continuity. When a new session starts, analyzes the system prompt and initial user messages to detect themes, entities, and topics. Then searches the knowledge graph and vector store to find relevant memories from past sessions. Also maintains "memory threads" —
↳ __init__(self, vector_store: VectorMemoryStore, knowledge_graph: KnowledgeGraph)
Initialize the session linker. Args: vector_store: Vector memory store for semantic search. knowledge_graph: Knowledge graph for relationship navigation.
↳ _extract_session_features(self, system_prompt: str, user_messages: Sequence[str])
Extract features from session start context. Analyzes system prompt and user messages to extract: - Topics (from keyword matching) - Entities (file paths, URLs, emails, commands) - Key phrases (bigra
↳ _score_memory_relevance(self, record: MemoryRecord, features: Dict[str, Any], vector_score: float = 0.0)
Calculate composite relevance score for a memory. Combines vector similarity, topic overlap, entity overlap, and keyword matching into a single score. Args: record: Memory record to score. f
↳ suggest_for_session(self, session_id: str, system_prompt: str = '', user_messages: Sequence[str] = (), top_k: int = 5)
Suggest relevant memories from past sessions. This is the main entry point called at session start. Args: session_id: Unique session identifier. system_prompt: The system prompt for this ses
↳ detect_session_continuity(self, session_id: str, system_prompt: str = '', user_messages: Sequence[str] = ())
Detect if this session continues work from a previous session. Analyzes the session context and checks the knowledge graph for connected previous sessions. Args: session_id: Current session ID.
↳ get_or_create_thread(self, session_id: str, suggested_memories: Sequence[Tuple[MemoryRecord, float]] = ())
Get existing thread or create a new one for this session. Args: session_id: Session identifier. suggested_memories: Memories suggested for this session (used to name the thread). Ret
↳ get_thread_for_session(self, session_id: str)
Get the thread a session belongs to. Args: session_id: Session identifier. Returns: MemoryThread or None.
↳ get_thread_memories(self, thread_id: str)
Get all memories in a thread with their session context. Args: thread_id: Thread identifier. Returns: List of (MemoryRecord, session_id).
↳ list_threads(self)
List all memory threads. Returns: List of MemoryThread objects, newest first.
↳ save(self, persist_dir: Path)
Save threads and session mapping to JSON. Args: persist_dir: Directory for persistence.
↳ load(self, persist_dir: Path)
Load threads and session mapping from JSON. Args: persist_dir: Directory for persistence.
Imports
__future__dataclasses datetime pathlib re threading typing
▶New folder/memory_v2/test_memory_v2.py 1143 LOC
Comprehensive tests for Memory V2 system. Run with: python -m pytest test_memory_v2.py -v Or: python test_memory_v2.py
Classes (9)
class TestTokenization
Tests for text tokenization and preprocessing.
↳ test_tokenize_basic(self)
↳ test_tokenize_removes_stopwords(self)
↳ test_tokenize_lowercase(self)
↳ test_tokenize_empty(self)
↳ test_tokenize_min_length(self)
↳ test_extract_entities_file_paths(self)
↳ test_extract_entities_urls(self)
↳ test_extract_entities_emails(self)
↳ test_extract_topics_docker(self)
↳ test_extract_topics_python(self)
↳ test_extract_key_phrases(self)
class TestMemoryEmbeddingEngine
Tests for the TF-IDF embedding engine.
↳ setUp(self)
↳ test_fit_creates_vocabulary(self)
↳ test_encode_returns_vector(self)
↳ test_encode_empty(self)
↳ test_similarity_same_vector(self)
↳ test_similarity_different_vectors(self)
↳ test_similarity_related_queries(self)
↳ test_similarity_orthogonal(self)
↳ test_similarities_batch(self)
↳ test_serialization(self)
↳ test_serialization_empty(self)
class TestVectorMemoryStore
Tests for the vector memory store.
↳ setUp(self)
↳ tearDown(self)
↳ _add_sample_memories(self)
↳ test_empty_store(self)
↳ test_add_and_get(self)
↳ test_add_batch(self)
↳ test_search_finds_relevant(self)
↳ test_search_scores_descending(self)
↳ test_search_with_scope(self)
↳ test_search_min_score(self)
↳ test_keyword_search(self)
↳ test_search_similar_to_memory(self)
↳ test_remove(self)
↳ test_remove_nonexistent(self)
↳ test_list_all_sorted(self)
↳ test_clear(self)
↳ test_persistence(self)
↳ test_stats(self)
↳ test_contains(self)
class TestKnowledgeGraph
Tests for the knowledge graph.
↳ setUp(self)
↳ tearDown(self)
↳ test_add_memory_node_creates_entities(self)
↳ test_add_memory_node_creates_topics(self)
↳ test_add_memory_node_creates_edges(self)
↳ test_neighbors(self)
↳ test_memories_for_entity(self)
↳ test_memories_for_topic(self)
↳ test_discover(self)
↳ test_related_memories(self)
↳ test_session_nodes(self)
↳ test_link_sessions(self)
↳ test_remove_memory_node(self)
↳ test_remove_nonexistent(self)
↳ test_stats(self)
↳ test_persistence(self)
↳ test_get_topics_summary(self)
↳ test_clear(self)
class TestSessionMemoryLinker
Tests for session memory linking.
↳ setUp(self)
↳ tearDown(self)
↳ _populate_memories(self)
↳ test_suggest_for_session_finds_relevant(self)
↳ test_suggest_empty_context(self)
↳ test_detect_session_continuity(self)
↳ test_get_or_create_thread(self)
↳ test_get_thread_for_session(self)
↳ test_persistence(self)
↳ test_list_threads(self)
class TestUnifiedMemoryQuery
Tests for the unified query engine.
↳ setUp(self)
↳ tearDown(self)
↳ test_keyword_search(self)
↳ test_semantic_search(self)
↳ test_graph_search(self)
↳ test_hybrid_search(self)
↳ test_hybrid_sorted(self)
↳ test_hybrid_weights(self)
↳ test_find_by_entity(self)
↳ test_find_by_topic(self)
↳ test_find_similar(self)
↳ test_discover_knowledge(self)
↳ test_explain_result(self)
↳ test_search_result_comparison(self)
class TestMemoryIndex
Tests for the master memory index.
↳ setUp(self)
↳ tearDown(self)
↳ test_add_memory(self)
↳ test_add_memory_with_tags(self)
↳ test_add_memory_gold(self)
↳ test_add_memory_from_markdown(self)
↳ test_remove_memory(self)
↳ test_list_memories(self)
↳ test_search(self)
↳ test_search_modes(self)
↳ test_get_related(self)
↳ test_discover(self)
↳ test_pin_unpin(self)
↳ test_get_graph_stats(self)
↳ test_get_memory_graph(self)
↳ test_register_session(self)
↳ test_suggest_for_new_session(self)
↳ test_get_session_thread(self)
↳ test_list_threads(self)
↳ test_export_all(self)
↳ test_stats(self)
↳ test_save_and_load(self)
class TestThreadSafety
Tests for thread safety across all components.
↳ setUp(self)
↳ tearDown(self)
↳ test_concurrent_adds(self)
Multiple threads adding memories concurrently.
↳ test_concurrent_search_and_add(self)
Search while adding memories.
class TestIntegration
End-to-end integration tests.
↳ setUp(self)
↳ tearDown(self)
↳ test_end_to_end_search(self)
Full search workflow.
↳ test_cross_memory_relationships(self)
Memories about the same topic should be related.
↳ test_session_workflow(self)
Full session registration and suggestion workflow.
↳ test_knowledge_discovery(self)
Knowledge discovery query.
↳ test_graph_stats_after_indexing(self)
Graph should have nodes and edges after indexing.
↳ test_memory_scope_filtering(self)
Memories should respect scope.
↳ test_persist_and_reload_integrity(self)
Data should survive save/load cycle.
Functions (1)
def temp_dir()
Create a temporary directory for test data.
Imports
__future__index json knowledge_graph numpy os pathlib query_engine session_linker shutil sys tempfile threading time unittest vector_store
▶New folder/memory_v2/vector_store.py 766 LOC
Vector Memory Store — TF-IDF based vector search with numpy. Lightweight, zero-dependency (beyond numpy) vector storage for memories. Uses Bag-of-Words + TF-IDF for embeddings and cosine similarity for search. All persistence is JSON-based. Thread-safe with file-level locking.
Classes (3)
class MemoryRecord
A single memory entry in the vector store. Attributes: id: Unique identifier (usually the memory filename without .md). content: Full text content of the memory. scope: Either "user" or "project". tags: Optional list of tags from frontmatter. gold: Whether this memory is pinned/
↳ to_dict(self)
Serialize to dict (excludes numpy vector).
↳ from_dict(cls, d: dict)
Deserialize from dict.
↳ text_for_embedding(self)
Text used for embedding: combines content with tags and metadata.
class MemoryEmbeddingEngine
TF-IDF based embedding engine for memories. Uses pure numpy — no sklearn, no transformers, no GPU needed. Builds a vocabulary across all memory texts and computes TF-IDF vectors. Example: >>> engine = MemoryEmbeddingEngine() >>> engine.fit(["Python code refactoring", "Doc
↳ __init__(self, min_df: int = 1, max_df_ratio: float = 0.95)
Initialize the embedding engine. Args: min_df: Minimum document frequency for a term to be included. max_df_ratio: Maximum document frequency ratio (terms appearing in more than this
↳ fit(self, documents: Sequence[str])
Build vocabulary and IDF from a corpus of documents. Args: documents: List of text documents to build vocabulary from. Returns: Self for chaining.
↳ partial_fit(self, new_documents: Sequence[str])
Incrementally extend vocabulary with new documents. Re-fits the entire corpus to maintain consistent IDF scores. Inefficient for large corpora — prefer batch fit(). Args: new_documents: New text
↳ vocab_size(self)
Current vocabulary size.
↳ encode(self, text: str)
Convert text to TF-IDF vector. Args: text: Input text to encode. Returns: 1D numpy float32 array of shape (vocab_size,).
↳ encode_query(self, query: str)
Encode a search query (same as encode, but explicit alias). Args: query: Search query text. Returns: 1D numpy float32 array.
↳ similarity(v1: np.ndarray, v2: np.ndarray)
Cosine similarity between two vectors. Args: v1: First vector. v2: Second vector (must match v1 shape). Returns: Cosine similarity in [-1, 1]. Returns 0.0 for empty vectors.
↳ similarities(self, query_vec: np.ndarray, matrix: np.ndarray)
Compute cosine similarities between query and all rows of a matrix. Args: query_vec: Query vector of shape (D,). matrix: Matrix of shape (N, D) where each row is a document vector. Returns:
↳ to_dict(self)
Serialize vocabulary and IDF to dict.
↳ from_dict(cls, d: dict)
Deserialize from dict.
class VectorMemoryStore
Persistent vector storage for memories with TF-IDF + cosine similarity search. Stores MemoryRecord objects, maintains TF-IDF vectors, and provides fast cosine-similarity search. All data persisted to JSON. Thread-safe. Example: >>> store = VectorMemoryStore(Path("~/.dulus/memory_v2/v
↳ __init__(self, persist_dir: Path, auto_save: bool = True)
Initialize the vector store. Args: persist_dir: Directory for JSON persistence. auto_save: Whether to auto-save on every mutation.
↳ _records_path(self)
↳ _engine_path(self)
↳ _save(self)
Persist records and engine state to JSON.
↳ _load(self)
Load records and engine state from JSON.
↳ _invalidate_cache(self)
Invalidate the cached vector matrix.
↳ _rebuild_vectors(self)
Rebuild all TF-IDF vectors from current records and engine.
↳ _build_matrix(self)
Build (or return cached) (N, D) matrix and id index. Returns: Tuple of (matrix, id_index) where matrix[i] is the vector for memory id_index[i].
↳ add(self, record: MemoryRecord)
Add or update a memory record. Re-fits the embedding engine and rebuilds all vectors. Args: record: MemoryRecord to store.
↳ add_batch(self, records: Sequence[MemoryRecord])
Add multiple records efficiently (single re-fit). Args: records: Sequence of MemoryRecords to store.
↳ get(self, memory_id: str)
Get a single record by ID. Args: memory_id: Memory identifier. Returns: MemoryRecord or None if not found.
↳ remove(self, memory_id: str)
Remove a memory by ID. Args: memory_id: Memory identifier. Returns: True if removed, False if not found.
↳ clear(self)
Remove all memories.
↳ list_all(self)
Return all stored records (newest first by modified_at).
↳ __len__(self)
↳ __iter__(self)
↳ __contains__(self, memory_id: str)
↳ search(self, query: str, top_k: int = 5, scope: Optional[str] = None, min_score: float = 0.0)
Semantic search via TF-IDF cosine similarity. Args: query: Search query text. top_k: Maximum number of results. scope: Filter by scope ("user" or "project"). min_score: Minimum simila
↳ search_similar_to_memory(self, memory_id: str, top_k: int = 5, min_score: float = 0.0)
Find memories similar to an existing memory. Args: memory_id: ID of the reference memory. top_k: Maximum results. min_score: Minimum similarity threshold. Returns: List of (MemoryRec
↳ keyword_search(self, query: str, top_k: int = 10, scope: Optional[str] = None)
Simple keyword-based search (exact token matching). Scores by the fraction of query tokens found in the memory text. Args: query: Search query. top_k: Max results. scope: Optional scope
↳ get_stats(self)
Return store statistics. Returns: Dict with count, vocab_size, persist_dir, etc.
Functions (2)
def tokenize(text: str)
Tokenize text into lowercase alphanumeric tokens, removing stopwords. Args: text: Raw text to tokenize. Returns: List of filtered tokens.
def extract_entities(text: str)
Extract named entities and patterns from text. Returns a dict with keys: file_paths, urls, emails, snake_case, camel_case, acronyms. Args: text: Text to analyze. Returns: Dictionary of entity types to lists of matches.
Imports
__future__dataclasses json math numpy os pathlib re threading time typing
▶New folder/refactor/core/__init__.py 11 LOC
Dulus Core — Refactored modules for the Dulus autonomous agent. Modules: theme — ANSI color system and theme management render — UI streaming, markdown, spinner, tool annotations session — Save/load/export session persistence commands — All /slash commands and the command dispatcher repl — Main REPL loop with run_query() and sentinel processing
▶New folder/refactor/core/commands.py 5466 LOC
All slash commands, the COMMANDS registry, handle_slash, and readline setup. This module contains every /command implementation for the Dulus REPL. Commands are registered in the COMMANDS dict and dispatched via handle_slash(). To add a new command: 1. Define cmd_<name>(args: str, state, config) -> bool | tuple 2. Add entry to COMMANDS dict 3. Optionally add to _CMD_META for tab-completion
Functions (92)
def ask_permission_interactive(desc: str, config: dict)
Ask the user for permission to execute a sensitive operation.
def _proactive_watcher_loop(config)
Background daemon that fires a wake-up prompt after a period of inactivity.
def cmd_help(_args: str, _state, config)
def cmd_model(args: str, _state, config)
def _generate_personas(topic: str, curr_model: str, config: dict, count: int = 5)
Ask the LLM to generate `count` topic-appropriate expert personas as a dict.
def _interactive_ollama_picker(config: dict)
Prompt the user to select from locally available Ollama models.
def cmd_brainstorm(args: str, state, config)
Run a multi-persona iterative brainstorming session on the project. Usage: /brainstorm [topic]
def _save_synthesis(state, out_file: str)
Append the last assistant response as the synthesis section of the brainstorm file.
def _print_dulus_banner(config: dict, with_logo: bool = True)
Reprint the Dulus logo + info box (used by startup and /clear).
def cmd_clear(_args: str, state, config)
def _redact_secret(value)
Mask all but last 4 chars of a secret value.
def _is_secret_key(key: str)
def cmd_config(args: str, _state, config)
def _atomic_write_json(path: Path, data)
Write JSON atomically: write to .tmp sibling, then rename. Prevents half-written files when the process is killed mid-save.
def _save_roundtable_session(log: list, save_path = None)
Save the full roundtable session log to a JSON file. Sessions go under config.MR_SESSION_DIR (~/.dulus/sessions/mr_sessions/), consistent with /save and other session artifacts. Pass an explicit save_path to override (used to keep all turns of one debate in one file).
def cmd_save(args: str, state, config)
def save_latest(args: str, state, config = None)
Save session on exit: session_latest.json + daily/ copy + append to history.json.
def cmd_load(args: str, state, config)
def cmd_resume(args: str, state, config)
def cmd_history(_args: str, state, config)
def cmd_context(_args: str, state, config)
def cmd_cost(_args: str, state, config)
def cmd_verbose(_args: str, _state, config)
def cmd_brave(_args: str, _state, config)
def cmd_git(_args: str, _state, config)
def cmd_webchat(args: str, _state, config)
Spawn the standalone webchat.py server in the background. /webchat stop kills it.
def cmd_max_fix(args: str, _state, config)
def cmd_thinking(_args: str, _state, config)
Set or toggle extended thinking. /thinking — toggle between OFF and the last non-zero level (default 2) /thinking 0|off — disable thinking entirely /thinking 1|min — minimal: low budget + "think briefly" prompt hint /thinking 2|med|medium — mod
def _normalize_thinking_level(value)
Coerce legacy bool/int/str thinking config into an int 0-4.
def cmd_soul(args: str, state, config)
List available souls or switch the active one mid-session. /soul — list souls + show active /soul <name> — switch to <name> (e.g. chill, forensic) by injecting it as an assistant message (same mechanism as startup load)
def cmd_schema(args: str, _state, _config)
Inspect tool schemas (human-facing; model doesn't see this command). /schema — list all registered tools, grouped /schema <tool> — show full input_schema + description for one tool /schema --json <t> — raw JSON dump of the tool's schema Useful for telling the agent
def cmd_deep_override(_args: str, _state, config)
def cmd_deep_tools(_args: str, _state, config)
def cmd_autojob(_args: str, _state, config)
def cmd_auto_show(_args: str, _state, config)
def cmd_schema_autoload(_args: str, _state, config)
Toggle auto-injection of the full tool schema inventory at startup. ON → at boot, the agent sees a system message listing every registered tool (name + description, grouped). Helps the model pick the right tool instead of reinventing via Bash. Costs ~3-5k chars per session. OFF → no in
def cmd_mem_palace(args: str, _state, config)
Toggle MemPalace per-turn memory injection. /mem_palace → toggle the injection ON/OFF /mem_palace print → toggle visibility: print to console what's being injected to the model (debug — see klk pasa) ON → before each user turn, runs `search_memory(query=user_msg
def cmd_harvest(_args: str, _state, config)
Harvest fresh cookies from claude.ai using Playwright. Opens a visible Chrome window with a persistent profile. If already logged in, cookies are collected automatically. If not, log in manually then press ENTER in the terminal. Cookies are saved to ~/.dulus/claude_cookies.json and any active clau
def cmd_harvest_kimi(_args: str, _state, config)
Harvest fresh gRPC tokens from kimi.com (Consumer) using Playwright. Opens a visible Chrome window and navigates to kimi.com. You must send a single message in the browser chat for the script to intercept the necessary gRPC-Web (Connect) headers and payloads. Data is saved to ~/.dulus/kimi_consume
def cmd_harvest_gemini(_args: str, _state, config)
Harvest fresh session data from gemini.google.com using Playwright. Opens a visible Chrome window and navigates to gemini.google.com. You must send a single message in the browser chat for the script to intercept the necessary internal API headers/cookies. Data is saved to ~/.dulus/gemini_web.json
def cmd_harvest_deepseek(_args: str, _state, config)
Harvest fresh session data from chat.deepseek.com using Playwright. Opens a visible Chrome window and navigates to chat.deepseek.com. The script intercepts the Authorization Bearer token and cookies automatically on the first chat response. Data is saved to ~/.dulus/deepseek_web.json for use by de
def cmd_gemini_chats(args: str, _state, config)
Manage Gemini Web conversations. /gemini_chats — show current conversation IDs /gemini_chats new — start a fresh conversation
def cmd_kimi_chats(args: str, _state, config)
List recent Kimi.com conversations (PLACEHOLDER).
def cmd_claude_chats(args: str, _state, config)
List and select Claude.ai conversations. /claude_chats — show last 20 conversations (numbered) /claude_chats all — show all conversations /claude_chats use <N> — switch to conversation #N from the list /claude_chats use <uuid> — switch to conversation by UUID prefix
def cmd_hide_sender(_args: str, _state, config)
Toggle echoing your typed message above the sticky input bar. ON → message disappears on send; output area shows only Dulus's responses (use /history to recall what you typed). OFF → your message stays visible above as `» <msg>`.
def cmd_history(args: str, state, _config)
Show previous user messages from this session. /history → last 20 user messages /history N → last N user messages /history all → all user messages
def cmd_sticky_input(_args: str, _state, config)
Toggle the prompt_toolkit anchored input bar. ON → input line stays pinned at the bottom; background notifications flow above it (can jitter on Windows consoles). OFF → plain input() — native terminal behavior, zero redraws. Background notifications land where they land.
def cmd_theme(args: str, _state, config)
Switch the Dulus color palette. `/theme` lists, `/theme <name>` applies.
def cmd_ultra_search(_args: str, _state, config)
def cmd_permissions(args: str, _state, config)
def cmd_cwd(args: str, _state, config)
def _build_session_data(state, session_id: str | None = None)
Serialize current conversation state to a JSON-serializable dict.
def cmd_cloudsave(args: str, state, config)
Sync sessions to GitHub Gist. /cloudsave setup <token> — configure GitHub Personal Access Token /cloudsave — upload current session to Gist /cloudsave push [desc] — same as above with optional description /cloudsave auto on|off — toggle auto-upload on /exit /cloudsav
def cmd_exit(_args: str, _state, config)
def cmd_memory(args: str, _state, config)
def cmd_agents(_args: str, _state, config)
def _print_background_notifications(state = None)
Print notifications and inject completions into state messages. Returns True if any NEW completion/failure was handled.
def _job_sentinel_loop(config, state)
Background daemon that triggers run_query as soon as a job finishes.
def cmd_skills(_args: str, _state, config)
def _pager(header: str, lines: list, page_size: int = 30)
Simple terminal pager: shows page_size lines, waits for n/q.
def cmd_skill(args: str, state, config)
Browse and install skills from Anthropic marketplace or ClawHub. /skill — list installed skills + show help /skill list — list installed skills /skill list local [q] — browse/search Anthropic skills on disk /skill list clawhub [q] — search ClawHub (WIP) /s
def cmd_mcp(args: str, _state, config)
Show MCP server status, or manage servers. /mcp — list all configured servers and their tools /mcp reload — reconnect all servers and refresh tools /mcp reload <name> — reconnect a single server /mcp add <name> <command> [args...] — add a stdio server to user
def cmd_plugin(args: str, _state, config)
Manage plugins. /plugin — list installed plugins /plugin install name@url [--main-agent] — install a plugin; with --main-agent, hand off to the main agent after install /plugin uninstall name — uninstall a plugin /plugin enable name
def cmd_tasks(args: str, _state, config)
Show and manage tasks. /tasks — list all tasks /tasks create <subject> — quick-create a task /tasks done <id> — mark task completed /tasks start <id> — mark task in_progress /tasks cancel <id> — mark task cancelled /tasks delete <id>
def cmd_ssj(args: str, state, config)
SSJ Developer Mode — Interactive power menu for project workflows. Usage: /ssj
def cmd_kill_tmux(_args: str, _state, config)
Kill all tmux and psmux sessions. Usage: /kill_tmux Useful when tmux/psmux sessions are stuck or causing problems.
def cmd_worker(args: str, state, config)
Auto-implement pending tasks from a todo_list.txt file. Usage: /worker — all pending tasks, default path /worker 1,4,6 — specific task numbers, default path /worker --path /some/todo.txt — all tasks from custom path /worker --path /
def _tg_api(token: str, method: str, params: dict = None)
Call Telegram Bot API. Returns parsed JSON or None on error.
def _tg_register_commands(token: str)
Register slash commands with Telegram so the native UI suggests them as the user types '/'. Called once when the bridge starts. Telegram rules: command name must be 1-32 chars, lowercase letters/digits/ underscores; description up to 256 chars; max 100 commands per bot.
def _tg_send(token: str, chat_id: int, text: str)
Send a message to a Telegram chat, splitting if too long.
def _tg_typing_loop(token: str, chat_id: int, stop_event: threading.Event, config: dict = None)
Send 'typing...' indicator every 4 seconds until stop_event is set.
def _tg_poll_loop(token: str, chat_id: int, config: dict)
Long-polling loop that reads Telegram messages and feeds them to run_query.
def cmd_telegram(args: str, _state, config)
Telegram bot bridge — receive and respond to messages via Telegram. Usage: /telegram <bot_token> <chat_id> — start the bridge /telegram stop — stop the bridge /telegram status — show current status First time: create a bot via @BotFat
def cmd_proactive(args: str, state, config)
Manage proactive background polling. /proactive — show current status /proactive 5m — enable, trigger after 5 min of inactivity /proactive 30s / 1h — enable with custom interval /proactive off — disable
def cmd_lite(args: str, state, config)
Toggle LITE mode - reduces system prompt from ~10K to ~500 tokens. /lite — toggle ON/OFF /lite on — force ON (minimal rules) /lite off — force OFF (full rules with all examples) LITE mode keeps only essential rules: - TmuxOffload for >5 seconds - SearchLastOutput for truncated
def cmd_tts(args: str, state, config)
TTS: toggle automatic voice output, or set language / auto-listen. /tts — toggle TTS ON/OFF /tts lang <code> — set language (es, en, fr, pt, ja…) /tts lang — show current language /tts auto — toggle auto-listen: after Dulus speaks, mic opens for
def cmd_say(args: str, state, config)
TTS: speak the provided text immediately. /say <text> — speak the given text using the best available backend
def cmd_voice(args: str, state, config)
Voice input: record → STT → auto-submit as user message. /voice — record once, transcribe, submit /voice status — show backend availability /voice lang <code> — set STT language (e.g. zh, en, ja; 'auto' to reset) /voice device — list and select input microphone
def cmd_image(args: str, state, config)
Grab image from clipboard and send to vision model with optional prompt.
def cmd_checkpoint(args: str, state, config)
List or restore checkpoints. /checkpoint — list all checkpoints /checkpoint <id> — restore to checkpoint #id /checkpoint clear — delete all checkpoints for this session
def cmd_plan(args: str, state, config)
Enter/exit plan mode or show current plan. /plan <description> — enter plan mode and start planning /plan — show current plan file contents /plan done — exit plan mode, restore permissions /plan status — show plan mode status
def cmd_compact(args: str, state, config)
Manually compact conversation history. /compact — compact with default summarization /compact <focus> — compact with focus instructions
def cmd_news(args: str, state, config)
Show the latest news from docs/news.md.
def cmd_init(args: str, state, config)
Initialize a DULUS.md file in the current directory. /init — create DULUS.md with a starter template
def cmd_export(args: str, state, config)
Export conversation history to a file. /export — export as markdown to .dulus/exports/ /export <filename> — export to a specific file (.md or .json)
def cmd_copy(args: str, state, config)
Copy the last assistant response to clipboard. /copy — copy last assistant message to clipboard
def cmd_status(args: str, state, config)
Show current session status. /status — model, provider, permissions, session info
def cmd_doctor(args: str, state, config)
Diagnose installation health and connectivity. /doctor — run all health checks
def cmd_roundtable(args: str, _state, config)
Start a roundtable discussion among different models. /roundtable - Enter setup mode to define models /roundtable stop - Exit roundtable mode /roundtable proactive 3m - Auto-send 'ok ok' every 3m to keep the table alive /roundtable proactive off - Disable roundtable proacti
def cmd_batch(args: str, _state, config)
Manage Kimi Batch tasks. /batch status [id] — check progress /batch list — list recent batch jobs /batch fetch [id] — download results when completed
def handle_slash(line: str, state, config)
Handle /command [args]. Returns True if handled, tuple (skill, args) for skill match.
def setup_readline(history_file: Path)
Imports
__future__argparse atexit base64 common core.render core.session datetime io json os pathlib random re shutil socket subprocess sys textwrap threading time typing urllib.error urllib.request uuid
▶New folder/refactor/core/render.py 300 LOC
UI rendering, streaming text, markdown, diff display, and spinner. Handles all visual output during Dulus's REPL: text streaming from the model, Rich Live display, tool call annotations, thinking blocks, and terminal spinners.
Functions (14)
def _rl_safe(prompt: str)
Wrap ANSI escape codes with / so readline calculates prompt width correctly and doesn't leave visual artefacts on long lines.
def render_diff(text: str)
Print a unified-diff hunk with colourised +/- lines.
def _has_diff(text: str)
def _make_renderable(text: str)
Wrap plain text in Rich renderable — uses Markdown when warranted.
def _start_live()
Start the Rich Live display if available.
def stream_text(chunk: str)
Stream a text chunk to the terminal. Uses Rich Live for in-place rendering when available; falls back to plain incremental output.
def flush_response()
Stop the Rich Live display (if active) and print any accumulated text to the terminal so subsequent prints appear below the response.
def _run_tool_spinner()
Background thread that prints a rotating spinner with a changing phrase.
def _start_tool_spinner(phrase: str | None = None)
Start the rotating spinner in a background thread.
def _change_spinner_phrase()
Pick a new random phrase while the spinner is running.
def _stop_tool_spinner()
Signal the spinner thread to stop and wait for it to finish.
def print_tool_start(name: str, inputs: dict, verbose: bool)
Print the start of a tool call.
def print_tool_end(name: str, result: str, verbose: bool, config: dict)
Print the end/result of a tool call.
def _tool_desc(name: str, inputs: dict)
Build a human-readable one-line description for a tool call.
Imports
__future__random re sys threading typing
▶New folder/refactor/core/repl.py 1500 LOC
Main REPL loop, run_query(), input handling, and sentinel processing. This is the heart of Dulus: the interactive read-eval-print loop that coordinates user input, model streaming, slash commands, sentinels, roundtable mode, batch mode, and background job processing.
Functions (1)
def repl(config: dict, initial_prompt: str = None)
Imports
__future__common core.commands core.render core.session core.theme pathlib random select sys threading time typing uuid
▶New folder/refactor/core/session.py 345 LOC
Session persistence: save, load, export, checkpoint management. All functions that deal with persisting or restoring conversation state, including auto-save, manual save/load, export to markdown/JSON, and checkpoint/rewind functionality.
Functions (13)
def _build_session_data(state, config: dict)
Serialize current state + config into a saveable dict.
def _redact_secret(value: str)
Mask API keys and secrets.
def _is_secret_key(key: str)
Check if a config key contains an API key or secret.
def save_latest(label: str, state, config: dict)
Auto-save the current session to the 'latest' slot.
def _rolling_save(data: dict, config: dict)
Keep last 10 sessions as numbered files (001..010).
def _safe_rmtree(path: Path)
Recursively delete a directory tree without following symlinks.
def _atomic_write_json(path: Path, data: dict)
Write JSON atomically using a temp file + rename.
def _save_roundtable_session(roundtable_log: list, save_path: Optional[Path] = None)
Save roundtable discussion log to JSON.
def cmd_save(args: str, state, config: dict)
Save the current session to a file. /save — save to .dulus/sessions/<timestamp>.json /save <filename> — save to specific file
def cmd_load(args: str, state, config: dict)
Load a saved session. /load — interactive picker of recent sessions /load <filename> — load specific file
def cmd_resume(args: str, state, config: dict)
Resume the last auto-saved session.
def cmd_export(args: str, state, config: dict)
Export conversation history to a file. /export — export as markdown to .dulus/exports/ /export <filename> — export to a specific file (.md or .json)
def cmd_copy(args: str, state, config: dict)
Copy the last assistant response to clipboard. /copy — copy last assistant message to clipboard
Imports
__future__datetime json os pathlib typing
▶New folder/refactor/core/theme.py 49 LOC
Theme and color system for Dulus. Thin wrapper around common.py's ANSI color and theme utilities. All Dulus UI rendering depends on these primitives.
Imports
common
▶New folder/refactor/dulus.py 202 LOC
Dulus — Next-gen Python Autonomous Agent Usage: dulus <prompt> Run a single prompt and enter interactive mode dulus -p <prompt> Run prompt in non-interactive mode (print and exit) dulus -m <model> Use specific model (e.g. gpt-4o, claude-sonnet) dulus --accept-all Auto-approve all operations dulus --verbose Show thinking + token counts dulus --thinking Enable extended thinking dulus --version
Functions (1)
def main()
Imports
__future__argparse sys uuid
▶New folder/resilience/__init__.py 72 LOC
Dulus RTK — Provider Resilience System Thread-safe, stdlib-only resilience layer for API providers. Usage: from resilience import make_providers_resilient, ResilienceConfig import providers state = make_providers_resilient(providers, ResilienceConfig.conservative()) # Access health health = state["health_monitor"].get_all_health() # Access circuit breaker state cb = state["circuit_breakers"]["openai"] print(cb.state) # Use fallback chain fc = state[
Imports
__future__core_resilience
▶New folder/resilience/core_resilience.py 1483 LOC
core_resilience.py — Resilience layer for Dulus RTK providers. Provides: CircuitBreaker, RetryPolicy, ProviderHealthMonitor, ResilientProviderWrapper, FallbackChain, AdaptiveRateLimiter, + make_providers_resilient() integration. Thread-safe, stdlib-only, production-ready.
Classes (12)
class CircuitBreakerOpen
Raised when the circuit breaker is OPEN and a call is attempted.
class ProviderUnavailable
Raised when all providers in a fallback chain are unavailable.
class RateLimitExceeded
Raised when the adaptive rate limiter blocks a request.
class CircuitState
class CircuitBreaker
Circuit breaker for provider API calls. States: CLOSED → Normal operation, all calls pass through. OPEN → After *failure_threshold* consecutive failures, all calls are rejected fast for *recovery_timeout* seconds. HALF_OPEN → After recovery_timeout, allow *half_open_max_
↳ __init__(self, name: str, failure_threshold: int = 5, recovery_timeout: float = 60.0, half_open_max_calls: int = 3, success_threshold_half_open: int = 1, expected_exception: Tuple[Type[Exception], ...] = (Exception,))
↳ state(self)
↳ can_execute(self)
Return True if a call should be allowed through.
↳ record_success(self)
Record a successful call.
↳ record_failure(self)
Record a failed call.
↳ record_rejected(self)
Record a call that was rejected because circuit was OPEN.
↳ call(self, fn: Callable, *args, **kwargs)
Execute *fn* under circuit-breaker protection. Raises CircuitBreakerOpen if the circuit is open.
↳ call_generator(self, fn: Callable, *args, **kwargs)
Execute a generator function under circuit-breaker protection. Yields from the generator and monitors for errors.
↳ get_metrics(self)
Return a snapshot of circuit breaker metrics.
↳ reset(self)
Manually reset the breaker to CLOSED.
↳ _maybe_transition(self)
Check if we should transition OPEN → HALF_OPEN based on time.
↳ _transition_to(self, new_state: CircuitState)
↳ _remaining_recovery(self)
class RetryPolicy
Retry configuration with exponential backoff and jitter. Args: max_retries: Maximum number of retry attempts (default: 3). base_delay: Initial delay in seconds (default: 1.0). max_delay: Maximum delay cap in seconds (default: 60.0). exponential_base: Base for exponential calculation
↳ compute_delay(self, attempt: int)
Compute delay for *attempt* (0-indexed).
↳ is_retryable(self, exc: Exception)
Check if *exc* is a retryable exception type.
↳ execute(self, fn: Callable, *args, **kwargs)
Execute *fn* with retry logic. Returns the result of fn() on success. Re-raises the last exception after exhausting retries.
↳ execute_generator(self, fn: Callable, *args, **kwargs)
Execute a generator function with retry logic. This is tricky because generators can't be "replayed". We retry the *entire* generator from the beginning on failure. For streaming, the caller must be
class HealthSnapshot
Snapshot of a provider's health at a point in time.
class ProviderHealthMonitor
Monitors the health of each provider with a sliding window. Tracks: - Latency (avg over sliding window) - Error rate (over sliding window) - Consecutive failures - Status: healthy / degraded / unhealthy Thread-safe.
↳ __init__(self, window_size: int = 100, degraded_error_rate: float = 0.25, unhealthy_error_rate: float = 0.75, degraded_latency_ms: float = 10000.0, unhealthy_latency_ms: float = 30000.0)
↳ record_request(self, provider_name: str, latency_ms: float, success: bool)
Record the result of a request.
↳ get_health(self, provider_name: str)
Get a health snapshot for *provider_name*.
↳ get_all_health(self)
Get health snapshots for all monitored providers.
↳ is_healthy(self, provider_name: str)
Quick check if provider is healthy. Returns True for unknown providers (no data yet).
↳ reset(self, provider_name: str)
Reset health data for a provider.
class AdaptiveRateLimiter
Adaptive rate limiter that responds to 429 responses and request history. Features: - Per-provider token bucket with configurable rate. - Backoff on 429 responses (reads Retry-After header if present). - Exponential backoff that decays over time. - Jitter to prevent thundering herd. - Thr
↳ __init__(self, default_requests_per_second: float = 2.0, burst_size: int = 5, backoff_factor: float = 2.0, max_backoff_seconds: float = 300.0, cooldown_decay: float = 0.5)
↳ set_rate(self, provider_name: str, requests_per_second: float)
Set a custom rate for a specific provider.
↳ acquire(self, provider_name: str, timeout: Optional[float] = None)
Try to acquire a token for *provider_name*. Returns True if acquired, False if timed out. Raises RateLimitExceeded if the backoff is too long.
↳ acquire_or_raise(self, provider_name: str, timeout: Optional[float] = None)
Acquire a token or raise RateLimitExceeded.
↳ report_429(self, provider_name: str, retry_after: Optional[float] = None)
Report a 429 response from the provider. Increases backoff and reduces effective rate.
↳ report_success(self, provider_name: str)
Report a successful request — decays the backoff.
↳ get_backoff_remaining(self, provider_name: str)
Get remaining backoff time for a provider.
↳ _ensure_bucket(self, provider_name: str)
↳ reset(self, provider_name: str)
Reset rate limiter state for a provider.
class FallbackChain
Chain of fallback providers. Attempts each provider in order until one succeeds. Records which provider in the chain succeeded for future reference. Thread-safe.
↳ __init__(self, chain: List[str], health_monitor: Optional[ProviderHealthMonitor] = None, circuit_breakers: Optional[Dict[str, CircuitBreaker]] = None, rate_limiters: Optional[Dict[str, AdaptiveRateLimiter]] = None, skip_unhealthy: bool = True, on_fallback: Optional[Callable[[str, str, Exception], None]] = None)
↳ last_successful(self)
↳ execute(self, fn_by_provider: Dict[str, Callable], *args, **kwargs)
Execute the function, trying each provider in the chain. *fn_by_provider* is a dict mapping provider_name → callable. The callable signature must be the same for all providers. Returns the result of
↳ execute_generator(self, fn_by_provider: Dict[str, Callable], *args, **kwargs)
Execute a generator function with fallback chain. Tries each provider's generator in order. If one fails mid-stream, we cannot recover from that generator — the caller receives the error. This is a l
↳ _record_success(self, provider_name: str)
↳ _provider_order(self)
Return provider order, prioritizing last successful.
↳ _next_after(self, provider_name: str)
Get the next provider after *provider_name* in the chain.
↳ get_metrics(self)
class ResilientProviderWrapper
Drop-in wrapper that adds resilience to any provider stream function. Combines CircuitBreaker + RetryPolicy + HealthMonitor + RateLimiter into a single wrapper that preserves the generator interface. Usage: wrapper = ResilientProviderWrapper("openai", stream_openai, ...) # wrapper is calla
↳ __init__(self, provider_name: str, stream_fn: Callable, circuit_breaker: Optional[CircuitBreaker] = None, retry_policy: Optional[RetryPolicy] = None, health_monitor: Optional[ProviderHealthMonitor] = None, rate_limiter: Optional[AdaptiveRateLimiter] = None, enable_retries_on_generator: bool = False, request_timeout: Optional[float] = None)
↳ __call__(self, *args, **kwargs)
Call the wrapped stream function with full resilience. Returns a generator that yields TextChunk/ThinkingChunk/AssistantTurn.
↳ _stream_with_resilience(self, *args, **kwargs)
Inner method that applies all resilience layers.
↳ _yield_from_generator(self, gen: Generator, start_time: float)
Yield from generator, recording success on completion.
↳ _is_rate_limit_error(e: Exception)
Heuristic to detect rate limit errors.
↳ _make_error_chunk(msg: str)
Create a TextChunk-like object for error messages.
↳ _make_error_turn(msg: str)
Create an AssistantTurn-like object for error finalization.
↳ get_stats(self)
class ResilienceConfig
Configuration for the resilience system. Sensible defaults for Dulus RTK providers.
↳ conservative(cls)
Very conservative settings — maximum resilience, slower recovery.
↳ aggressive(cls)
Aggressive settings — fast recovery, more retries.
Functions (5)
def make_providers_resilient(providers_module: Any, config: Optional[ResilienceConfig] = None, providers: Optional[List[str]] = None, fallback_chains: Optional[Dict[str, List[str]]] = None)
Wrap all provider streaming functions with resilience. This is the main integration function. It wraps the provider functions in *providers_module* (e.g., the imported providers.py module) with CircuitBreaker + RetryPolicy + HealthMonitor + RateLimiter. Args: providers_module: The imported pro
def _find_stream_function(providers_module: Any, provider_name: str)
Find the stream function for a provider in the module. Mapping: anthropic → stream_anthropic openai → stream_openai_compat (with openai base_url) gemini → stream_openai_compat (with gemini base_url) kimi → stream_kimi moonshot → stream_kimi kimi-code
def _patch_providers_module(providers_module: Any, wrappers: Dict[str, ResilientProviderWrapper])
Monkey-patch the providers module so existing code uses wrapped functions. This modifies: - stream_anthropic → wrappers['anthropic'] - stream_kimi → wrappers['kimi'] - stream_ollama → wrappers['ollama'] - etc.
def resilient_call(provider_name: str, stream_fn: Callable, *args, **kwargs)
One-shot resilient call to a provider stream function. Creates a temporary wrapper and calls it. Useful for quick integration. Example: for chunk in resilient_call("openai", stream_openai_compat, api_key, ...): print(chunk)
def get_system_health(resilience_state: dict)
Get a comprehensive health report for all providers. Pass the dict returned by make_providers_resilient().
Imports
__future__collections dataclasses enum functools logging random threading time typing
▶New folder/resilience/test_resilience.py 1116 LOC
test_resilience.py — Exhaustive tests for the resilience system. Run with: python -m pytest test_resilience.py -v python test_resilience.py # unittest mode
Classes (12)
class DummyException
Test-specific exception.
class TransientException
Simulates a transient error (network blip).
class TestCircuitBreaker
Tests for CircuitBreaker.
↳ test_initial_state_is_closed(self)
↳ test_record_success_in_closed(self)
↳ test_opens_after_threshold_failures(self)
↳ test_blocks_calls_when_open(self)
↳ test_records_rejected(self)
↳ test_half_open_after_recovery_timeout(self)
↳ test_half_open_allows_limited_calls(self)
↳ test_closes_on_half_open_success(self)
↳ test_opens_on_half_open_failure(self)
↳ test_call_success(self)
↳ test_call_propagates_exception(self)
↳ test_reset(self)
↳ test_get_metrics(self)
↳ test_expected_exception_filtering(self)
↳ test_generator_wrap_success(self)
↳ test_generator_wrap_failure(self)
↳ test_thread_safety(self)
↳ test_state_transitions_logged(self)
class TestRetryPolicy
Tests for RetryPolicy.
↳ test_success_no_retry(self)
↳ test_retries_then_succeeds(self)
↳ test_exhausts_retries(self)
↳ test_non_retryable_raises_immediately(self)
↳ test_delay_computation(self)
↳ test_delay_with_jitter(self)
↳ test_delay_max_cap(self)
↳ test_generator_retry(self)
Generator retries restart from beginning — includes partial output from failed attempts.
↳ test_generator_exhausts_retries(self)
↳ test_on_retry_callback(self)
↳ test_is_retryable(self)
class TestProviderHealthMonitor
Tests for ProviderHealthMonitor.
↳ test_record_and_get_health(self)
↳ test_error_rate_calculation(self)
↳ test_status_healthy(self)
↳ test_status_degraded_by_error_rate(self)
↳ test_status_unhealthy(self)
↳ test_status_degraded_by_latency(self)
↳ test_sliding_window(self)
↳ test_unknown_provider(self)
↳ test_is_healthy(self)
↳ test_get_all_health(self)
↳ test_reset(self)
↳ test_consecutive_failures(self)
↳ test_timestamps(self)
↳ test_thread_safety(self)
class TestAdaptiveRateLimiter
Tests for AdaptiveRateLimiter.
↳ test_acquire_success(self)
↳ test_acquire_multiple(self)
↳ test_acquire_with_timeout(self)
↳ test_acquire_or_raise(self)
↳ test_report_429_backoff(self)
↳ test_report_429_consecutive_escalation(self)
↳ test_report_429_with_retry_after(self)
↳ test_report_success_decays_backoff(self)
↳ test_set_rate(self)
↳ test_per_provider_isolation(self)
↳ test_reset(self)
↳ test_token_replenishment(self)
↳ test_thread_safety_acquire(self)
class TestFallbackChain
Tests for FallbackChain.
↳ test_single_provider_success(self)
↳ test_fallback_on_failure(self)
↳ test_all_fail_raises(self)
↳ test_fallback_chain_execute_order(self)
Verify that fallback chain tries providers in order.
↳ test_prioritizes_last_successful(self)
↳ test_skips_unhealthy_when_configured(self)
↳ test_does_not_skip_when_skip_false(self)
↳ test_skip_open_circuit(self)
↳ test_on_fallback_callback(self)
↳ test_get_metrics(self)
↳ test_generator_fallback(self)
↳ test_generator_all_fail(self)
↳ test_missing_fn_skipped(self)
class TestResilientProviderWrapper
Tests for ResilientProviderWrapper.
↳ test_successful_call(self)
↳ test_error_turn(self)
↳ test_circuit_breaker_blocks(self)
↳ test_with_retry_policy(self)
↳ test_retry_exhausted(self)
↳ test_health_monitor_recording(self)
↳ test_health_monitor_failure(self)
↳ test_rate_limiter_blocks(self)
↳ test_wrapper_stats(self)
↳ test_is_rate_limit_error(self)
class TestResilienceConfig
Tests for ResilienceConfig.
↳ test_default_values(self)
↳ test_conservative(self)
↳ test_aggressive(self)
↳ test_provider_overrides(self)
class TestGetSystemHealth
Tests for get_system_health.
↳ test_empty_state(self)
↳ test_with_providers(self)
class TestResilientCall
Tests for resilient_call convenience function.
↳ test_success(self)
↳ test_with_circuit_breaker(self)
class TestIntegrationEdgeCases
Integration and edge case tests.
↳ test_circuit_opens_under_load(self)
Simulate a provider failing under load — circuit should open.
↳ test_rate_limiter_with_429_cascade(self)
Simulate cascading 429s — backoff should increase.
↳ test_fallback_chain_with_health(self)
Full integration: fallback chain + health monitor + circuit breaker.
↳ test_concurrent_access_health_monitor(self)
Stress test: many threads recording to health monitor.
↳ test_retry_with_jitter_no_collision(self)
Many retries with jitter should not cause issues.
↳ test_generator_partial_yield_then_fail(self)
Generator yields some chunks then fails — wrapper should handle it.
↳ test_empty_generator(self)
Empty generator should succeed with no chunks.
↳ test_non_generator_return(self)
Function that doesn't return a generator — wrapper returns it as-is.
Functions (4)
def make_failing_fn(fail_count: int, exc_class = DummyException)
Return a function that fails *fail_count* times, then succeeds.
def make_failing_generator(fail_count: int, exc_class = DummyException)
Return a generator that yields then fails *fail_count* times, then succeeds.
def make_success_fn(return_val = 'ok')
Return a function that always succeeds.
def make_success_generator(chunks: list = None)
Return a generator function that yields chunks.
Imports
__future__core_resilience os random sys threading time typing unittest
▶New folder/security/__init__.py 42 LOC
Dulus RTK — Security & Hardening module. Provides sandboxing, audit trails, secret management, granular permissions, output sanitisation, and file-access control for autonomous agent systems. All components are pure-Python stdlib, thread-safe, and designed for integration with the Dulus RTK agent loop. Example:: from security import ( CommandSandbox, FileAccessController, AuditTrail, SecretManager, PermissionManager, PermissionLevel, OutputSanitizer,
Imports
audit permissions sandbox sanitizer secret_manager
▶New folder/security/audit.py 470 LOC
audit.py — Immutable append-only audit trail with hash-chain integrity. Provides: - AuditTrail : thread-safe append-only log where each entry includes the SHA-256 hash of the previous entry, forming a tamper-evident chain. - Export to JSON / CSV. - Automatic log rotation by entry count. All functionality uses the Python stdlib only.
Classes (2)
class AuditEntry
A single audit-trail entry.
↳ to_dict(self)
class AuditTrail
Immutable append-only audit log with hash-chain integrity. Each entry contains the SHA-256 hash of the previous entry, forming a tamper-evident chain. The log file is rotated automatically when the number of entries exceeds *max_entries*. Thread-safe via internal ``RLock``. Example:: audit
↳ __init__(self, log_file: str | Path, max_entries: int = 10000, max_file_size_mb: int = 100, session_id: str | None = None, auto_flush: bool = True)
Initialise the audit trail. Args: log_file: Path to the JSONL log file. max_entries: Maximum entries before rotation. max_file_size_mb: Maximum file size (MB) before rotation. session
↳ log_tool_call(self, tool: str, params: dict[str, Any], result: str, tokens_in: int = 0, tokens_out: int = 0, depth: int = 0)
Log a tool execution.
↳ log_permission(self, tool: str, params: dict[str, Any], action: str, reason: str = '', depth: int = 0)
Log a permission decision (*action* = ``grant`` or ``deny``).
↳ log_model_change(self, old_model: str, new_model: str, depth: int = 0)
Log a model switch.
↳ log_session_event(self, event_type: str, details: dict[str, Any], depth: int = 0)
Log a generic session event.
↳ log_security_event(self, event_type: str, details: str, tool: str = '', params: Optional[dict[str, Any]] = None, depth: int = 0)
Log a security-related event (block, alert, anomaly).
↳ verify_chain(self)
Verify the integrity of the hash chain. Returns: *(valid, first_bad_sequence)* where *valid* is *True* if the chain is intact, and *first_bad_sequence* is the sequence number of the first
↳ get_last_hash(self)
Return the hash of the most recent entry.
↳ export_json(self, output_path: str | Path)
Export the entire trail as a JSON array. Returns entry count.
↳ export_csv(self, output_path: str | Path)
Export the trail as CSV. Returns entry count.
↳ query(self, event_type: str | None = None, tool: str | None = None, since: str | None = None, limit: int = 100)
Query entries with optional filters. Args: event_type: Filter by event type. tool: Filter by tool name. since: ISO timestamp; only entries after this time. limit: Maximum entries to r
↳ _maybe_rotate(self)
Rotate log if size or entry limit exceeded.
↳ _create_entry(self, event_type: str, tool: str, params: dict[str, Any], result: str, tokens_in: int = 0, tokens_out: int = 0, permission_action: str = '', model: str = '', depth: int = 0)
Build an :class:`AuditEntry` (without the hash yet).
↳ _append(self, entry: AuditEntry)
Append *entry* to the log file.
↳ _compute_hash(self, entry_dict: dict[str, Any])
Compute the SHA-256 hash of an entry (excluding its own hash field).
↳ _recover(self)
Recover sequence and last-hash from an existing log file.
↳ _read_all_entries(self)
Read all entries from the log file.
↳ _generate_session_id()
Generate a unique session ID.
↳ stats(self)
Return audit trail statistics.
↳ entry_count(self)
Return the number of entries in the current log.
Imports
__future__csv dataclasses datetime hashlib json os pathlib threading time typing
▶New folder/security/permissions.py 431 LOC
permissions.py — Granular permission manager for Dulus RTK. Provides: - PermissionLevel : enum-like constants for permission tiers. - PermissionManager: RBAC-style permission manager that supports global, per-session, per-sub-agent, and per-tool permission levels. All functionality uses the Python stdlib only.
Classes (3)
class PermissionLevel
Permission tier for a category of operations. Levels are ordered from most restrictive to least restrictive: ``READ_ONLY < SAFE_WRITE < UNSAFE_WRITE < NETWORK < SHELL < SHELL_UNSAFE``.
↳ __ge__(self, other: PermissionLevel)
↳ __gt__(self, other: PermissionLevel)
↳ __le__(self, other: PermissionLevel)
↳ __lt__(self, other: PermissionLevel)
class PermissionDecision
Result of a permission check.
class PermissionManager
Granular RBAC-style permission manager. Supports four layers of configuration (resolved in order of precedence): 1. **Per-tool override** — highest priority. 2. **Per-sub-agent level** — set via ``set_agent_level(agent_id, level)``. 3. **Per-session level** — set via ``set_session_level(level)``.
↳ __init__(self, default_level: PermissionLevel = PermissionLevel.READ_ONLY, work_dir: str | None = None, mode: str = 'auto')
Initialise the permission manager. Args: default_level: Global fallback permission level. work_dir: Directory used for SAFE_WRITE confinement checks. mode: Legacy mode name for backward c
↳ set_global_level(self, level: PermissionLevel)
Set the global default permission level.
↳ set_session_level(self, level: PermissionLevel)
Set the current session permission level.
↳ clear_session_level(self)
Remove the session-level override.
↳ set_agent_level(self, agent_id: str, level: PermissionLevel)
Set a permission level for a specific sub-agent.
↳ remove_agent_level(self, agent_id: str)
Remove a sub-agent permission override.
↳ allow_tool(self, tool_name: str)
Explicitly allow *tool_name* regardless of level.
↳ deny_tool(self, tool_name: str)
Explicitly deny *tool_name* regardless of level.
↳ remove_tool_override(self, tool_name: str)
Remove a tool-level override.
↳ set_mode(self, mode: str)
Set the legacy mode (``auto`` | ``manual`` | ``accept-all`` | ``plan``).
↳ check(self, tool_name: str, params: dict[str, Any], depth: int = 0, agent_id: str | None = None)
Check whether *tool_name* is permitted. Args: tool_name: Name of the tool to check. params: Tool input parameters (used for work-dir validation). depth: Sub-agent nesting depth. agent
↳ is_permitted(self, tool_name: str, params: dict[str, Any] | None = None)
Convenience: return *True* if the tool is permitted.
↳ _effective_level(self, agent_id: str | None = None)
Resolve the effective permission level for the current context. Precedence: per-tool override > agent > session > global.
↳ _update_counts(self, permitted: bool)
Update allow/deny counters.
↳ legacy_check(self, tool_name: str, params: dict[str, Any], plan_file: str = '', safe_bash_checker: Any = None)
Backward-compatible check that mimics the old ``_check_permission``. Args: tool_name: Tool name. params: Tool parameters. plan_file: Plan-mode target file path. safe_bash_checker: Cal
↳ stats(self)
Return permission statistics.
↳ reset_stats(self)
Reset counters.
↳ snapshot(self)
Return a full snapshot of the current configuration.
Functions (1)
def _tool_level(tool_name: str)
Return the minimum :class:`PermissionLevel` required for *tool_name*.
Imports
__future__dataclasses enum threading typing
▶New folder/security/sandbox.py 798 LOC
sandbox.py — Sandboxing for Bash commands and filesystem access. Provides: - CommandSandbox : whitelist/blacklist analysis, dangerous-command detection, timeout enforcement, directory restrictions, and resource limits. - FileAccessController : read/write gating that protects sensitive paths and confines writes to a configurable working directory. All functionality uses the Python stdlib only.
Classes (4)
class SandboxResult
Result of a sandbox policy check.
class ExecutionResult
Result of executing a sandboxed command.
class CommandSandbox
Sandbox for Bash command execution. Provides multiple layers of protection: 1. **Pattern blocking**: Regex-based detection of dangerous command patterns. 2. **Binary gating**: Known-dangerous binaries trigger elevated risk scores. 3. **Directory restrictions**: Commands may be restricted to a
↳ __init__(self, work_dir: str | Path | None = None, blocked_patterns: Optional[list[str]] = None, allowed_patterns: Optional[list[str]] = None, max_memory_mb: int = 512, max_cpu_time_sec: int = 60, max_file_size_mb: int = 100, max_output_size: int = 10 * 1024 * 1024, enable_resource_limits: bool = True, custom_blocklist: Optional[list[str]] = None, custom_allowlist: Optional[list[str]] = None)
Initialise the sandbox. Args: work_dir: Directory to which writes are restricted (``chroot-like``). blocked_patterns: Additional regex patterns to block. allowed_patterns: Regex patterns
↳ check(self, command: str)
Analyse *command* against the sandbox policy. Returns a :class:`SandboxResult` indicating whether the command is permitted, the reason if denied, a sanitized copy, and a risk score.
↳ is_safe(self, command: str)
Return *True* if *command* passes the sandbox policy.
↳ run(self, command: str, timeout: int = 30, cwd: str | Path | None = None, env: dict[str, str] | None = None, shell: bool = True)
Run *command* under sandbox restrictions. The command is first validated via :meth:`check`. If denied, an :class:`ExecutionResult` with ``returncode=-1`` and the reason in ``stderr`` is returned. R
↳ _calculate_risk_score(self, command: str)
Calculate a heuristic risk score [0.0, 1.0].
↳ _is_within_work_dir(self, command: str)
Heuristic: does *command* stay within the configured work_dir?
↳ _detect_external_write(self, command: str)
Heuristic: does the command attempt to write outside the work dir?
↳ _sanitize_command(self, command: str)
Return a redacted version of *command* suitable for logging.
↳ stats(self)
Return sandbox statistics.
↳ reset_stats(self)
Reset all counters.
class FileAccessController
Restrict file-system access to a safe subset. Enforces: * **Working-directory confinement**: writes are confined to a single directory tree. * **Sensitive-path protection**: blocks reads/writes to ``.ssh/``, ``.aws/``, ``.env``, etc. * **Overwrite confirmation**: important files require expli
↳ __init__(self, work_dir: str | Path | None = None, allow_read_outside_work_dir: bool = True, allow_write_outside_work_dir: bool = False, protected_paths: Optional[list[str]] = None, confirm_overwrite_callback: Optional[Callable[[str], bool]] = None)
Initialise the file-access controller. Args: work_dir: Root directory for confined writes. allow_read_outside_work_dir: If *False*, reads outside *work_dir* are blocked. allow_write_outsi
↳ can_read(self, file_path: str | Path)
Return *(allowed, reason)* for a read operation.
↳ can_write(self, file_path: str | Path)
Return *(allowed, reason)* for a write operation.
↳ can_edit(self, file_path: str | Path)
Alias for ``can_write`` — edits are treated as writes.
↳ _is_sensitive(self, path_str: str)
Check whether *path_str* contains a sensitive component.
↳ _is_important_file(self, filename: str)
Check whether *filename* matches important-file patterns.
↳ resolve(self, file_path: str | Path)
Return an absolute, resolved path.
↳ relative_to_work(self, file_path: str | Path)
Return *file_path* relative to *work_dir*, or the absolute path.
↳ stats(self)
Return access-control statistics.
↳ reset_stats(self)
Reset counters.
Functions (1)
def shlex_quote(s: str)
Minimal shlex.quote replacement (stdlib).
Imports
__future__dataclasses os pathlib re resource shutil signal subprocess threading time typing uuid
▶New folder/security/sanitizer.py 311 LOC
sanitizer.py — Output sanitisation to prevent information leakage. Provides: - OutputSanitizer : masks API keys, tokens, and sensitive paths in text outputs; detects common secret patterns and session identifiers. All functionality uses the Python stdlib only.
Classes (3)
class SanitizationFinding
A single finding from sanitization scanning.
class SanitizationResult
Result of sanitizing a text block.
↳ to_dict(self)
class OutputSanitizer
Sanitise text outputs to prevent information leakage. Detects and masks: * API keys (Anthropic, OpenAI, AWS, GitHub, Google, Slack, generic) * Session tokens (JWT, Bearer, Basic auth, session cookies) * Sensitive paths (``.ssh/``, ``.aws/``, ``.env``, ``/etc/shadow``, etc.) * Other sensitiv
↳ __init__(self, mask_replacement: str = '***', mask_prefix_chars: int = 4, mask_suffix_chars: int = 4, enable_secrets: bool = True, enable_tokens: bool = True, enable_paths: bool = True, enable_other: bool = True)
Initialise the sanitizer. Args: mask_replacement: String used for masking when position-based replacement is not possible. mask_prefix_chars: Characters to preserve at start of masked
↳ sanitize(self, text: str)
Scan and sanitize *text*, returning a :class:`SanitizationResult`. All detected sensitive values are replaced with masked versions in the returned ``sanitized_text``.
↳ mask_secrets(self, text: str)
Return *text* with all detected secrets masked. Convenience wrapper.
↳ has_secrets(self, text: str)
Return *True* if *text* contains detectable secrets.
↳ has_sensitive_paths(self, text: str)
Return *True* if *text* contains sensitive file paths.
↳ has_tokens(self, text: str)
Return *True* if *text* contains session/auth tokens.
↳ _apply_patterns(self, text: str, patterns: list[tuple[str, re.Pattern]], category: str)
Apply a list of patterns to *text*, collecting findings and masking matches. Uses a replace-in-reverse strategy to preserve string positions.
↳ _mask_value(self, value: str)
Create a masked version of *value*.
↳ quick_scan(self, text: str)
Quick scan that returns boolean flags for each category.
↳ add_secret_pattern(self, name: str, pattern: str)
Add a custom secret-detection regex pattern.
↳ add_token_pattern(self, name: str, pattern: str)
Add a custom token-detection regex pattern.
↳ add_path_pattern(self, name: str, pattern: str)
Add a custom sensitive-path detection regex pattern.
↳ stats(self)
Return sanitizer statistics.
↳ reset_stats(self)
Reset counters.
Imports
__future__dataclasses re threading typing
▶New folder/security/secret_manager.py 439 LOC
secret_manager.py — Secure secret storage and API-key lifecycle. Provides: - SecretManager : encrypts secrets at rest using AES-like construction built from ``hashlib`` and ``hmac`` (stdlib only); supports key-scoped access, automatic masking, secret rotation, and leak detection in text outputs. No external cryptography dependencies.
Classes (3)
class _SimpleCipher
Simple AES-like cipher built from hashlib/hmac primitives. Uses AES-256-CTR-like construction: * Key is 256-bit derived via PBKDF2-like iteration. * Keystream is generated by SHA-256 in CTR mode. * Ciphertext = plaintext XOR keystream. This is **NOT** a replacement for real cryptography — it
↳ __init__(self, master_password: str | bytes)
↳ derive_key(self, salt: bytes)
Derive a 256-bit key from the master password + salt.
↳ _keystream(self, key: bytes, nonce: bytes, length: int)
Generate a keystream via CTR-mode SHA-256.
↳ encrypt(self, plaintext: bytes, password: str | bytes | None = None)
Encrypt *plaintext*. Returns ``salt + nonce + ciphertext``.
↳ decrypt(self, data: bytes, password: str | bytes | None = None)
Decrypt data produced by :meth:`encrypt`.
↳ _derive_with_password(self, password: bytes, salt: bytes)
Derive key from an arbitrary password.
class SecretEntry
A stored secret with metadata.
class SecretManager
Secure storage and lifecycle management for API keys and secrets. Features: * **Encryption at rest**: Secrets are encrypted with a stdlib-based cipher. * **Scope-based access**: Each secret is bound to a provider scope; only that scope can retrieve it. * **Masking**: Secrets are never sho
↳ __init__(self, master_password: str | bytes, storage_path: str | Path | None = None, rotation_days: int = 90, max_access_before_rotation: int = 10000)
Initialise the secret manager. Args: master_password: Master password for encryption. storage_path: Optional JSON file for persistent storage. rotation_days: Number of days after which a
↳ set_secret(self, scope: str, secret: str | bytes, metadata: Optional[dict[str, Any]] = None)
Store a secret for *scope*. Args: scope: Provider or service scope (e.g. ``openai``, ``anthropic``). secret: The raw secret string or bytes. metadata: Optional metadata dictionary. Retur
↳ get_secret(self, scope: str)
Retrieve the raw secret for *scope*. .. warning:: Only call this at the API boundary — never log the result.
↳ get_masked(self, scope: str)
Return the masked preview of a secret (safe for logging).
↳ has_secret(self, scope: str)
Return *True* if a secret exists for *scope*.
↳ remove_secret(self, scope: str)
Remove the secret for *scope*. Returns *True* if it existed.
↳ rotate_secret(self, scope: str, new_secret: str | bytes)
Rotate the secret for *scope*. Returns: The new *key_id*.
↳ list_scopes(self)
Return all stored scope names.
↳ scan_for_secrets(self, text: str)
Scan *text* for exposed API-key-like patterns. Returns: List of dicts with ``type``, ``match``, and ``masked`` keys.
↳ sanitize_text(self, text: str)
Return *text* with any detected secrets masked out.
↳ rotation_status(self, scope: str)
Return rotation metadata for a secret.
↳ all_rotation_status(self)
Return rotation status for all scopes.
↳ _make_mask(secret: str, visible_prefix: int = 4, visible_suffix: int = 4)
Create a masked representation of *secret*. Shows *visible_prefix* characters, then ``...``, then *visible_suffix* characters at the end.
↳ _persist(self)
Save encrypted secrets to disk.
↳ _load(self)
Load encrypted secrets from disk.
↳ stats(self)
Return secret manager statistics.
Imports
__future__dataclasses hashlib hmac json os pathlib re secrets threading time typing uuid
▶New folder/security/test_security.py 1289 LOC
test_security.py — Exhaustive tests for the Dulus RTK security module. Run with: python -m unittest test_security -v Covers: - CommandSandbox (patterns, risk scoring, execution, stats) - FileAccessController (reads, writes, sensitive paths, work_dir) - AuditTrail (hash chain, rotation, export, verification) - SecretManager (encryption, masking, rotation, leak detection) - PermissionManager (levels, overrides, backward compat) - OutputSanitizer (secret masking, path detection, tok
Classes (35)
class TestCommandSandboxBlockedPatterns
Tests for dangerous-command pattern blocking.
↳ test_rm_rf_root(self)
↳ test_rm_rf_star(self)
↳ test_rm_rf_home(self)
↳ test_rm_rf_root_with_flags(self)
↳ test_dd_disk_destroy(self)
↳ test_dd_nvme(self)
↳ test_redirect_to_disk(self)
↳ test_mkfs(self)
↳ test_fdisk(self)
↳ test_chmod_777_root(self)
↳ test_fork_bomb(self)
↳ test_curl_pipe_bash(self)
↳ test_curl_pipe_sh(self)
↳ test_wget_pipe_bash(self)
↳ test_cat_shadow(self)
↳ test_cat_passwd(self)
↳ test_curl_data_upload(self)
↳ test_curl_upload_file(self)
↳ test_nc_exec(self)
↳ test_bash_reverse_shell(self)
↳ test_python_reverse_shell(self)
↳ test_sysctl_write(self)
↳ test_swapoff(self)
↳ test_kill_all(self)
↳ test_history_wipe(self)
class TestCommandSandboxSafeCommands
Tests that safe commands are permitted.
↳ test_ls(self)
↳ test_cat(self)
↳ test_git_status(self)
↳ test_git_log(self)
↳ test_find(self)
↳ test_grep(self)
↳ test_pwd(self)
↳ test_echo(self)
↳ test_df(self)
↳ test_curl_head(self)
↳ test_empty_command(self)
class TestCommandSandboxCustomLists
Tests for custom allowlist/blocklist.
↳ test_custom_allowlist(self)
↳ test_custom_blocklist(self)
↳ test_custom_blocked_pattern(self)
↳ test_custom_allowed_pattern(self)
class TestCommandSandboxRiskScoring
Tests for risk-score calculation.
↳ test_sudo_elevates_risk(self)
↳ test_pipe_to_shell_risk(self)
↳ test_safe_command_zero_risk(self)
↳ test_dangerous_binary_score(self)
↳ test_subshell_modest_risk(self)
class TestCommandSandboxExecution
Tests for sandboxed command execution.
↳ test_run_allowed_command(self)
↳ test_run_blocked_command(self)
↳ test_run_timeout(self)
↳ test_run_with_cwd(self)
↳ test_stats_after_run(self)
↳ test_reset_stats(self)
class TestFileAccessControllerBasic
Basic read/write permission tests.
↳ test_read_allowed(self)
↳ test_write_allowed(self)
↳ test_read_ssh_denied(self)
↳ test_read_aws_denied(self)
↳ test_read_shadow_denied(self)
↳ test_write_env_denied(self)
↳ test_write_ssh_denied(self)
↳ test_write_docker_config_denied(self)
class TestFileAccessControllerWorkDir
Work-directory confinement tests.
↳ setUp(self)
↳ tearDown(self)
↳ test_write_inside_work_dir(self)
↳ test_write_outside_work_dir_denied(self)
↳ test_read_outside_work_dir_when_disallowed(self)
↳ test_write_outside_when_allowed(self)
↳ test_relative_to_work(self)
class TestFileAccessControllerOverwrite
Overwrite confirmation tests.
↳ setUp(self)
↳ tearDown(self)
↳ test_overwrite_important_file_denied(self)
↳ test_overwrite_with_callback_allowed(self)
↳ test_overwrite_with_callback_denied(self)
class TestFileAccessControllerDangerousExtensions
Dangerous extension blocking tests.
↳ test_exe_extension_denied(self)
↳ test_dll_extension_denied(self)
class TestFileAccessControllerStats
Statistics tests.
↳ test_stats_tracking(self)
↳ test_reset_stats(self)
class TestAuditTrailBasic
Basic audit trail logging tests.
↳ setUp(self)
↳ tearDown(self)
↳ test_log_tool_call(self)
↳ test_log_permission(self)
↳ test_log_model_change(self)
↳ test_sequence_increments(self)
↳ test_session_id_present(self)
↳ test_session_id_auto_generated(self)
class TestAuditTrailHashChain
Hash chain integrity tests.
↳ setUp(self)
↳ tearDown(self)
↳ test_first_entry_prev_hash_is_genesis(self)
↳ test_entry_hash_not_empty(self)
↳ test_chain_links_correctly(self)
↳ test_verify_empty_chain(self)
↳ test_verify_valid_chain(self)
↳ test_detects_tampering(self)
↳ test_last_hash_updated(self)
class TestAuditTrailRotation
Log rotation tests.
↳ setUp(self)
↳ tearDown(self)
↳ test_rotation_by_entry_count(self)
class TestAuditTrailExport
Export functionality tests.
↳ setUp(self)
↳ tearDown(self)
↳ test_export_json(self)
↳ test_export_csv(self)
↳ test_query_by_event_type(self)
↳ test_query_by_tool(self)
↳ test_query_limit(self)
class TestAuditTrailStats
Statistics tests.
↳ setUp(self)
↳ tearDown(self)
↳ test_stats_after_logging(self)
class TestSecretManagerBasic
Basic secret storage and retrieval tests.
↳ test_set_and_get(self)
↳ test_set_and_get_bytes(self)
↳ test_get_masked(self)
↳ test_has_secret(self)
↳ test_remove_secret(self)
↳ test_get_nonexistent_raises(self)
↳ test_list_scopes(self)
class TestSecretManagerEncryption
Encryption/decryption tests.
↳ test_encryption_does_not_store_plaintext(self)
↳ test_different_passwords_yield_different_ciphertext(self)
↳ test_roundtrip_encryption(self)
class TestSecretManagerRotation
Secret rotation tests.
↳ test_rotate_secret(self)
↳ test_rotation_status_age(self)
↳ test_rotation_status_needs_rotation(self)
class TestSecretManagerLeakDetection
Secret leak detection tests.
↳ test_detect_openai_key(self)
↳ test_detect_aws_key(self)
↳ test_detect_known_secret_leak(self)
↳ test_sanitize_text(self)
↳ test_leak_detection_increments_counter(self)
class TestSecretManagerPersistence
Persistence tests.
↳ setUp(self)
↳ tearDown(self)
↳ test_persist_and_load(self)
↳ test_access_count_persists(self)
class TestPermissionManagerLevels
Permission level ordering and comparison tests.
↳ test_level_ordering(self)
↳ test_read_tools_at_read_only(self)
↳ test_write_tools_need_safe_write(self)
↳ test_write_at_safe_write(self)
↳ test_network_tools_need_network(self)
↳ test_network_at_network_level(self)
↳ test_bash_needs_shell(self)
↳ test_bash_at_shell_level(self)
class TestPermissionManagerSessionOverrides
Session-level override tests.
↳ test_session_level_elevation(self)
↳ test_session_level_reduction(self)
↳ test_clear_session_level(self)
class TestPermissionManagerAgentOverrides
Per-agent override tests.
↳ test_agent_level(self)
↳ test_agent_level_does_not_affect_others(self)
↳ test_remove_agent_level(self)
class TestPermissionManagerToolOverrides
Per-tool override tests.
↳ test_allow_tool(self)
↳ test_deny_tool(self)
↳ test_remove_tool_override(self)
class TestPermissionManagerWorkDir
Work-directory restriction for SAFE_WRITE.
↳ test_write_inside_work_dir(self)
↳ test_write_outside_work_dir_denied(self)
class TestPermissionManagerLegacy
Backward-compatibility mode tests.
↳ test_accept_all_mode(self)
↳ test_manual_mode(self)
↳ test_legacy_check_auto(self)
class TestPermissionManagerStats
Statistics tests.
↳ test_stats(self)
↳ test_snapshot(self)
class TestOutputSanitizerSecrets
API-key masking tests.
↳ test_mask_openai_key(self)
↳ test_mask_anthropic_key(self)
↳ test_mask_aws_key(self)
↳ test_no_false_positives(self)
class TestOutputSanitizerTokens
Session/auth token detection tests.
↳ test_detect_jwt(self)
↳ test_detect_bearer(self)
class TestOutputSanitizerPaths
Sensitive path detection tests.
↳ test_detect_ssh_path(self)
↳ test_detect_aws_credentials_path(self)
↳ test_detect_env_file(self)
↳ test_detect_etc_shadow(self)
class TestOutputSanitizerOther
Other sensitive data detection.
↳ test_password_in_text(self)
↳ test_quick_scan(self)
class TestOutputSanitizerConvenience
Convenience method tests.
↳ test_mask_secrets(self)
↳ test_has_secrets_true(self)
↳ test_has_secrets_false(self)
class TestOutputSanitizerCustomPatterns
Custom pattern registration tests.
↳ test_add_secret_pattern(self)
class TestOutputSanitizerStats
Statistics tests.
↳ test_stats(self)
↳ test_reset_stats(self)
class TestIntegration
Cross-module integration tests.
↳ test_sandbox_blocks_command_with_secret(self)
Sandbox should not care about secrets in commands, but sanitizer should mask them.
↳ test_full_security_stack(self)
Simulate a full request going through the security stack.
↳ test_concurrent_access(self)
Thread-safety smoke test.
Imports
__future__json os pathlib security shutil sys tempfile threading time unittest
▶New folder/testing/conftest.py 260 LOC
Shared pytest fixtures for the Dulus RTK comprehensive test suite. These fixtures provide: - tmp_path-based filesystem isolation - Environment variable isolation - Mocked external dependencies (subprocess, HTTP) - Provider configuration helpers
Functions (12)
def _dummy_register_offload_tool(*args, **kwargs)
No-op mock for memory.offload.register_offload_tool
def _disable_http_external_requests()
Ensure no real HTTP requests go out during tests.
def _clear_env_vars()
Clear potentially sensitive env vars before each test.
def _reset_provider_cache()
Clear any cached provider state between tests.
def sample_config()
Standard config fixture for provider/tool tests.
def provider_config()
Config with provider-specific keys set.
def tool_schemas()
Minimal tool schema set for registry tests.
def sample_messages()
A simple conversation for provider message tests.
def mock_state(sample_messages)
Mock conversation state object.
def tmp_dulus_home(tmp_path, monkeypatch)
Redirect ~/.dulus to a temp directory.
def mock_anthropic_stream()
Factory for mock anthropic stream responses.
def mock_openai_sse_chunks()
Factory for mock SSE chunks from OpenAI-compatible APIs.
Imports
__future__os pathlib pytest sys types unittest.mock
▶New folder/testing/test_common_comprehensive.py 457 LOC
Comprehensive tests for common.py — Common UI/formatting utilities. Tests: apply_theme, clr, info, ok, warn, err, stream_thinking, print_tool_start, print_tool_end, sanitize_text, setup_slash_commands, read_slash_input, reset_slash_session.
Classes (13)
class TestRgb
↳ test_basic(self)
↳ test_black(self)
↳ test_white(self)
class TestApplyTheme
↳ test_all_themes(self)
↳ test_unknown_theme(self)
↳ test_sets_code_theme(self)
↳ test_dulus_theme(self)
↳ test_matrix_theme(self)
↳ test_mono_theme(self)
↳ test_red_always_red(self)
Red should always stay red across all themes.
↳ test_themes_have_required_keys(self)
↳ test_themes_are_hex_colors(self)
↳ test_reapply_same_theme(self)
↳ test_switch_themes(self)
class TestClr
↳ test_single_color(self)
↳ test_multiple_colors(self)
↳ test_text_conversion(self)
Non-string text should be converted.
↳ test_empty_string(self)
↳ test_coerces_to_string(self)
class TestInfoOkWarnErr
↳ test_info(self, mock_print)
↳ test_ok(self, mock_print)
↳ test_warn(self, mock_print)
↳ test_err(self, mock_print)
↳ test_info_includes_color_codes(self, mock_print)
class TestStreamThinking
↳ test_verbose_true(self, mock_print)
↳ test_verbose_false(self, mock_print)
↳ test_newlines_replaced(self, mock_print)
↳ test_empty_chunk(self, mock_print)
class TestPrintToolStart
↳ test_basic(self, mock_print)
↳ test_bash_tool(self, mock_print)
↳ test_write_tool(self, mock_print)
↳ test_no_inputs(self, mock_print)
↳ test_has_ansi_codes(self, mock_print)
class TestPrintToolEnd
↳ test_success(self, mock_print)
↳ test_failure(self, mock_print)
↳ test_short_result(self, mock_print)
↳ test_verbose_shows_preview(self, mock_print)
↳ test_print_to_console_special(self, mock_print)
↳ test_display_only_tool(self, mock_print)
Display-only tools (if registered) show full content.
↳ test_unicode_result(self, mock_print)
class TestSanitizeText
↳ test_clean_text(self)
↳ test_empty_string(self)
↳ test_removes_surrogates(self)
U+D800-U+DFFF should be removed.
↳ test_removes_high_surrogate(self)
↳ test_removes_low_surrogate(self)
↳ test_all_surrogate_range(self)
↳ test_non_string_input(self)
↳ test_preserves_valid_unicode(self)
class TestThemesRegistry
↳ test_minimum_themes(self)
↳ test_dulus_exists(self)
↳ test_all_have_code_theme(self)
↳ test_known_themes(self)
class TestC
↳ test_has_required_keys(self)
↳ test_reset_is_escape(self)
↳ test_bold_is_escape(self)
↳ test_dim_is_escape(self)
class TestCodeTheme
↳ test_is_string(self)
↳ test_not_empty(self)
class TestSlashCompleterFallbacks
↳ test_setup_slash_commands_returns_bool(self)
↳ test_read_slash_input_is_callable(self)
↳ test_reset_slash_session_is_callable(self)
↳ test_read_slash_input_returns_string(self)
class TestCommonEdgeCases
↳ test_clr_with_nonexistent_color_key(self)
Using an unknown key raises KeyError (current behavior).
↳ test_apply_theme_corrupts_nothing(self)
Applying a theme should never break the C dict structure.
↳ test_stream_thinking_none(self)
stream_thinking raises AttributeError on None (current behavior).
Imports
__future__common pytest sys unittest.mock
▶New folder/testing/test_compaction_comprehensive.py 550 LOC
Comprehensive tests for compaction.py — Context window management. Tests: estimate_tokens, get_context_limit, snip_old_tool_results, find_split_point, compact_messages, maybe_compact, manual_compact, _restore_plan_context.
Classes (9)
class TestEstimateTokens
↳ test_empty_list(self)
↳ test_simple_string_content(self)
↳ test_multiple_messages(self)
↳ test_with_tool_calls(self)
↳ test_list_content(self)
↳ test_reasonable_estimate(self)
1000 chars should give ~400-500 tokens with the formula.
↳ test_kimi_model_with_api_key(self)
If Kimi model and API key available, should try native estimation.
↳ test_kimi_model_fallback(self)
If Kimi native fails, falls back to char-based.
↳ test_moonshot_alias(self)
↳ test_no_model(self)
estimate_tokens without model param works.
↳ test_tool_calls_in_message(self)
Tool calls with string values contribute to count.
class TestGetContextLimit
↳ test_anthropic_model(self)
↳ test_openai_model(self)
↳ test_gemini_model(self)
↳ test_kimi_model(self)
↳ test_ollama_model(self)
↳ test_deepseek_model(self)
↳ test_unknown_model_fallback(self)
↳ test_with_provider_prefix(self)
↳ test_various_providers(self, model, expected)
class TestSnipOldToolResults
↳ test_no_tool_messages(self, simple_messages)
↳ test_old_tool_truncated(self, messages_with_tool_results)
↳ test_preserve_recent(self, messages_with_tool_results)
↳ test_short_content_not_snipped(self, messages_with_tool_results)
↳ test_non_string_content_ignored(self)
↳ test_returns_same_list(self, messages_with_tool_results)
snip_old_tool_results mutates in place and returns the same list.
↳ test_cutoff_zero(self, messages_with_tool_results)
When all messages are within preserve window, nothing is snipped.
↳ test_empty_messages(self)
class TestFindSplitPoint
↳ test_even_split(self, simple_messages)
↳ test_keep_all(self, simple_messages)
↳ test_keep_none(self, simple_messages)
↳ test_single_message(self)
↳ test_empty_messages(self)
↳ test_ratio_0_3(self, long_messages)
↳ test_with_model_and_config(self, simple_messages, mock_config)
class TestCompactMessages
↳ test_below_threshold(self, mock_stream, simple_messages, mock_config)
When under threshold, split <= 0 → returns original.
↳ test_compacts(self, mock_stream, long_messages, mock_config)
↳ test_with_focus(self, mock_stream, long_messages, mock_config)
↳ test_empty_summary(self, mock_stream, long_messages, mock_config)
↳ test_messages_returned_when_split_zero(self, simple_messages, mock_config)
If find_split_point returns 0 or less, original messages returned.
↳ test_structured_content_handling(self, mock_stream, mock_config)
Messages with list content should be handled.
class TestMaybeCompact
↳ test_below_threshold_no_compact(self, mock_state, mock_config)
↳ test_layer1_sufficient(self, mock_snip, mock_est, mock_state, mock_config)
Layer 1 (snip) brings us below threshold → True without layer 2.
↳ test_layer2_compact(self, mock_compact, mock_est, mock_state, mock_config)
Both layers needed.
↳ test_model_from_config(self, mock_state)
class TestManualCompact
↳ test_not_enough_messages(self)
↳ test_successful_compact(self, mock_compact, mock_config)
↳ test_with_focus(self, mock_compact, mock_config)
↳ test_reports_token_savings(self, mock_config)
class TestRestorePlanContext
↳ test_no_plan_file(self, tmp_path, mock_config)
↳ test_not_in_plan_mode(self, tmp_path, mock_config)
↳ test_plan_file_missing(self, tmp_path, mock_config)
↳ test_plan_file_empty(self, tmp_path, mock_config)
↳ test_plan_file_content(self, tmp_path, mock_config)
class TestCompactionEdgeCases
↳ test_estimate_tokens_empty_content(self)
↳ test_estimate_tokens_no_content_key(self)
↳ test_snip_ignores_non_tool(self)
↳ test_find_split_single_msg(self)
↳ test_find_split_two_msgs(self)
↳ test_compact_messages_empty(self, mock_config)
↳ test_compact_messages_one_message(self, mock_config)
Functions (5)
def simple_messages()
def long_messages()
Messages that total a lot of characters.
def messages_with_tool_results()
def mock_config()
def mock_state(simple_messages)
Minimal state-like object with .messages.
Imports
__future__compaction pytest sys unittest.mock
▶New folder/testing/test_config_comprehensive.py 449 LOC
Comprehensive tests for config.py — Configuration management. Tests: load_config, save_config, current_provider, has_api_key, calc_cost, backward-compat (api_key → anthropic_api_key), ENV_BRIDGE.
Classes (9)
class TestDefaults
↳ test_defaults_not_empty(self)
↳ test_default_model(self)
↳ test_default_permission_mode(self)
↳ test_default_max_tokens_positive(self)
↳ test_default_max_tool_output(self)
↳ test_default_max_agent_depth(self)
↳ test_default_session_limits(self)
↳ test_shell_config(self)
↳ test_all_defaults_are_json_serializable(self)
All defaults must be JSON-serializable for save_config.
↳ test_no_underscore_keys_in_defaults(self)
Defaults should not have internal runtime keys.
class TestLoadConfig
↳ test_creates_directories(self)
↳ test_returns_defaults_when_no_file(self)
↳ test_merges_existing_config(self, tmp_path)
↳ test_ignores_broken_json(self, tmp_path)
↳ test_backward_compat_api_key(self, tmp_path)
Legacy 'api_key' → 'anthropic_api_key'.
↳ test_backward_compat_no_overwrite(self, tmp_path)
Don't overwrite existing anthropic_api_key with legacy api_key.
↳ test_env_var_anthropic(self, monkeypatch)
↳ test_config_file_overrides_env(self, tmp_path, monkeypatch)
Config file takes priority over env vars.
class TestEnvBridge
↳ test_nvidia_web_api_key(self, tmp_path, monkeypatch)
↳ test_openai_api_key(self, tmp_path, monkeypatch)
↳ test_gemini_api_key(self, tmp_path, monkeypatch)
↳ test_deepseek_api_key(self, tmp_path, monkeypatch)
↳ test_kimi_api_key(self, tmp_path, monkeypatch)
↳ test_kimi_code_api_key(self, tmp_path, monkeypatch)
↳ test_env_bridge_does_not_overwrite_existing(self, monkeypatch, tmp_path)
Bridge only sets env vars if not already present.
↳ test_empty_key_no_bridge(self, tmp_path, monkeypatch)
Empty string keys should not be bridged.
class TestSaveConfig
↳ test_creates_directory(self)
↳ test_saves_valid_json(self)
↳ test_strips_internal_keys(self)
Keys starting with _ should be stripped.
↳ test_preserves_user_keys(self)
↳ test_roundtrip(self)
load → modify → save → load should preserve changes.
↳ test_idempotent(self)
Saving the same config twice should produce same output.
class TestCurrentProvider
↳ test_anthropic(self)
↳ test_openai(self)
↳ test_gemini(self)
↳ test_ollama(self)
↳ test_default_model(self)
When model not in config, falls back to hardcoded default.
class TestHasApiKey
↳ test_no_key(self)
↳ test_with_key(self)
↳ test_ollama_no_key_needed(self)
Ollama uses hardcoded 'ollama' key → always has key.
↳ test_lmstudio_no_key_needed(self)
LM Studio uses hardcoded 'lm-studio' key → always has key.
↳ test_env_var_key(self, monkeypatch)
↳ test_with_config_key(self)
class TestCalcCost
↳ test_known_model(self)
↳ test_unknown_model(self)
↳ test_zero_tokens(self)
class TestConfigPaths
↳ test_config_dir_is_path(self)
↳ test_config_file_inside_dir(self)
↳ test_history_file_name(self)
↳ test_sessions_dir(self)
↳ test_daily_dir(self)
↳ test_mr_session_dir(self)
class TestConfigEdgeCases
↳ test_load_config_with_null_values(self, tmp_path)
↳ test_load_config_with_nested_dict(self, tmp_path)
↳ test_save_config_with_list(self)
↳ test_save_config_with_numbers(self)
↳ test_load_multiple_times(self)
Multiple loads should be idempotent.
Functions (2)
def _isolate_config(tmp_path, monkeypatch)
Redirect config dir to temp path so tests don't touch ~/.dulus.
def _clear_env()
Imports
__future__config json os pathlib pytest sys unittest.mock
▶New folder/testing/test_context_comprehensive.py 496 LOC
Comprehensive tests for context.py — System context building. Tests: build_system_prompt, get_git_info, get_dulus_md, get_project_memory_index, _detect_shell_type, get_platform_hints, _normalize_thinking_level, _build_ollama_system_prompt.
Classes (9)
class TestNormalizeThinkingLevel
↳ test_true(self)
↳ test_false(self)
↳ test_none(self)
↳ test_int_0(self)
↳ test_int_1(self)
↳ test_int_4(self)
↳ test_clamped_high(self)
↳ test_clamped_low(self)
↳ test_string_number(self)
↳ test_invalid_string(self)
↳ test_no_config(self)
class TestGetGitInfo
↳ test_in_git_repo(self, mock_check)
↳ test_not_git_repo(self, mock_check)
↳ test_git_disabled_in_config(self, mock_check)
↳ test_clean_repo(self, mock_check)
↳ test_multiple_modified_files(self, mock_check)
class TestGetDulusMd
↳ test_no_files(self, tmp_path, monkeypatch)
↳ test_global_dulus_md(self, tmp_path, monkeypatch)
↳ test_project_dulus_md(self, tmp_path, monkeypatch)
↳ test_both_global_and_project(self, tmp_path, monkeypatch)
↳ test_unicode_content(self, tmp_path, monkeypatch)
class TestGetProjectMemoryIndex
↳ test_no_memory_dir(self, tmp_path, monkeypatch)
↳ test_with_memory_index(self, tmp_path, monkeypatch)
↳ test_empty_index(self, tmp_path, monkeypatch)
↳ test_searches_parents(self, tmp_path, monkeypatch)
Looks in cwd parents if not in cwd.
↳ test_permission_denied(self, tmp_path, monkeypatch)
class TestDetectShellType
↳ test_bash(self)
↳ test_shell_with_bash(self)
↳ test_powershell(self)
↳ test_configured_gitbash(self)
↳ test_configured_wsl(self)
↳ test_configured_powershell(self)
↳ test_configured_cmd(self)
↳ test_configured_bash(self)
↳ test_auto_no_env(self)
When auto and no SHELL, falls back to cmd.
↳ test_no_config(self)
None config defaults to auto → cmd on Windows-like env.
↳ test_custom_shell_type(self)
class TestGetPlatformHints
↳ test_unix(self, mock_plat)
↳ test_windows(self, mock_plat)
↳ test_macos(self, mock_plat)
↳ test_returns_string(self)
↳ test_includes_dulus_home(self)
↳ test_includes_skills_dir(self)
class TestBuildOllamaSystemPrompt
↳ test_returns_string(self)
↳ test_includes_rules(self)
↳ test_includes_date(self)
↳ test_auto_show_on(self)
↳ test_auto_show_off(self)
↳ test_includes_dulus_md(self, tmp_path, monkeypatch)
class TestBuildSystemPrompt
↳ test_returns_string(self, base_config)
↳ test_includes_date(self, base_config)
↳ test_includes_cwd(self, base_config)
↳ test_includes_platform(self, base_config)
↳ test_auto_show_on(self, base_config)
↳ test_auto_show_off(self, base_config)
↳ test_includes_platform_hints(self, base_config)
↳ test_deepseek_r1_override(self, base_config)
↳ test_thinking_label_level_1(self, base_config)
↳ test_thinking_label_level_3(self, base_config)
↳ test_thinking_label_disabled(self, base_config)
↳ test_plan_mode(self, base_config)
↳ test_project_memories(self, base_config, tmp_path, monkeypatch)
↳ test_no_config(self)
build_system_prompt should work with None.
↳ test_batch_info(self, base_config)
↳ test_no_duplicate_newlines(self, base_config)
class TestSystemPromptTemplate
↳ test_is_string(self)
↳ test_has_placeholders(self)
↳ test_format_with_all_placeholders(self)
Functions (2)
def _clear_dulus_md(tmp_path, monkeypatch)
Prevent real DULUS.md files from interfering.
def base_config()
Imports
__future__context os pathlib pytest sys unittest.mock
▶New folder/testing/test_providers_comprehensive.py 1116 LOC
Comprehensive tests for providers.py — Dulus RTK multi-provider streaming layer. Tests: detect_provider, bare_model, get_api_key, calc_cost, WebToolParser, _format_web_tool_manifest, _consolidate_web_history, tools_to_openai, messages_to_anthropic, messages_to_openai, scrub_any_type, stream dispatcher, friendly_api_error, list_ollama_models, estimate_tokens_kimi, _thinking_level_from, plus all 11+ provider stream functions (mocked).
Classes (21)
class TestDetectProvider
Exhaustive model-string → provider mapping.
↳ test_detect_provider(self, model, expected)
class TestBareModel
↳ test_bare_model(self, model, expected)
class TestGetApiKey
↳ test_from_config_direct(self)
↳ test_from_env_var(self, monkeypatch)
↳ test_config_overrides_env(self, monkeypatch)
↳ test_moonshot_kimi_alias(self)
↳ test_kimi_moonshot_alias(self)
↳ test_kimi_code_key(self)
↳ test_ollama_hardcoded(self)
↳ test_lmstudio_hardcoded(self)
↳ test_unknown_provider_empty(self)
↳ test_all_env_var_providers(self, provider, env_var, monkeypatch)
class TestCalcCost
↳ test_known_model(self)
↳ test_known_model_bare(self)
↳ test_known_model_with_prefix(self)
↳ test_unknown_model_zero(self)
↳ test_all_costs_have_tuple(self)
class TestWebToolParser
↳ test_basic_text_pass_through(self)
↳ test_empty_chunk(self)
↳ test_single_tool_call(self)
↳ test_text_then_tool_call(self)
↳ test_multiple_tool_calls(self)
↳ test_tool_call_with_function_format(self)
↳ test_tool_call_split_across_chunks(self)
↳ test_flush_with_incomplete_tool_call(self)
↳ test_auto_wrap_json_disabled_by_default(self)
↳ test_auto_wrap_json_enabled(self)
↳ test_auto_wrap_json_with_function_format(self)
↳ test_auto_wrap_json_mixed_with_text(self)
class TestFormatWebToolManifest
↳ test_no_tools(self)
↳ test_no_tools_flag(self)
↳ test_first_turn_injects(self)
↳ test_second_turn_no_inject(self)
↳ test_always_inject_override(self)
↳ test_empty_messages_first_turn(self)
class TestConsolidateWebHistory
↳ test_empty_messages(self)
↳ test_empty_with_manifest(self)
↳ test_simple_user_message(self)
↳ test_skips_empty_non_tool(self)
↳ test_keeps_empty_tool_result(self)
↳ test_tool_result_with_name(self)
↳ test_after_last_assistant(self)
↳ test_manifest_prepended(self)
class TestToolsToOpenAI
↳ test_basic_conversion(self)
↳ test_parameters_fallback(self)
Uses 'parameters' key if 'input_schema' is missing.
↳ test_empty_params_fallback(self)
If no schema key, falls back to empty object.
↳ test_scrub_any_type(self)
'any' type should be scrubbed from schema.
↳ test_invalid_entry_skipped(self)
class TestScrubAnyType
↳ test_dict_with_any(self)
↳ test_dict_without_any(self)
↳ test_nested_list(self)
↳ test_nested_dict(self)
↳ test_primitives_unchanged(self)
class TestMessagesToAnthropic
↳ test_simple_user(self)
↳ test_assistant_with_text(self)
↳ test_assistant_with_thinking(self)
↳ test_assistant_with_tool_calls(self)
↳ test_tool_result(self)
↳ test_consecutive_tools_grouped(self)
class TestMessagesToOpenAI
↳ test_simple_user(self)
↳ test_user_with_images(self)
↳ test_user_with_images_ollama(self)
↳ test_assistant_with_content(self)
↳ test_assistant_with_thinking(self)
↳ test_assistant_with_tool_calls(self)
↳ test_sanitizes_orphan_tool_calls(self)
Tool calls without matching tool responses should be stripped.
↳ test_tool_message(self)
class TestFriendlyApiError
↳ test_auth_error(self)
↳ test_rate_limit(self)
↳ test_overloaded(self)
↳ test_context_length(self)
↳ test_bad_request(self)
↳ test_connection_error(self)
↳ test_fallback(self)
↳ test_by_exception_type(self)
class TestThinkingLevelFrom
↳ test_true(self)
↳ test_false(self)
↳ test_none(self)
↳ test_int(self)
↳ test_clamped_high(self)
↳ test_clamped_low(self)
↳ test_invalid_string(self)
class TestEstimateTokensKimi
↳ test_no_api_key(self)
↳ test_successful_estimate(self, mock_urlopen)
↳ test_missing_total_tokens(self, mock_urlopen)
↳ test_request_failure(self, mock_urlopen)
↳ test_multimodal_messages(self)
Messages with list content should be handled.
class TestListOllamaModels
↳ test_success(self, mock_urlopen)
↳ test_empty_models(self, mock_urlopen)
↳ test_connection_error(self, mock_urlopen)
class TestProviderRegistry
↳ test_all_providers_have_type(self)
↳ test_known_providers(self)
↳ test_context_limits_positive(self)
class TestBuildPromptToolManifest
↳ test_basic(self)
↳ test_multiple_tools(self)
↳ test_workflow_example(self)
class TestStreamDispatcher
↳ _collect(self, gen: Generator)
↳ test_anthropic_dispatch(self, mock_stream)
↳ test_kimi_dispatch(self, mock_stream)
↳ test_ollama_dispatch(self, mock_stream)
↳ test_openai_dispatch(self, mock_stream)
↳ test_gemini_dispatch(self, mock_stream)
↳ test_deepseek_dispatch(self, mock_stream)
↳ test_qwen_dispatch(self, mock_stream)
↳ test_zhipu_dispatch(self, mock_stream)
↳ test_minimax_dispatch(self, mock_stream)
↳ test_claude_web_dispatch(self, mock_stream)
↳ test_kimi_web_dispatch(self, mock_stream)
↳ test_nvidia_web_dispatch(self, mock_stream)
↳ test_custom_no_base_url_raises(self)
↳ test_custom_with_base_url(self, mock_stream)
↳ test_custom_base_url_from_env(self, mock_stream, monkeypatch)
↳ test_claude_code_dispatch(self, mock_stream)
class TestStreamKimiNative
↳ test_basic_streaming(self, mock_urlopen)
↳ test_tool_calls_in_stream(self, mock_urlopen)
↳ test_error_response(self, mock_urlopen)
class TestStreamOllama
↳ test_basic_ollama_stream(self, mock_urlopen)
↳ test_ollama_with_thinking(self, mock_urlopen)
↳ test_ollama_http_error(self, mock_urlopen)
class TestEventClasses
↳ test_text_chunk(self)
↳ test_thinking_chunk(self)
↳ test_assistant_turn(self)
↳ test_assistant_turn_error(self)
↳ test_assistant_turn_defaults(self)
Functions (4)
def _clear_env()
Clean env vars that tests touch.
def base_config()
def sample_tool_schemas()
def sample_messages()
Imports
__future__json os pathlib providers pytest sys types typing unittest.mock urllib.error
▶New folder/testing/test_tools_comprehensive.py 1298 LOC
Comprehensive tests for tools.py — Dulus RTK tool implementations. Covers: Read, Write, Edit, Bash, Glob, Grep, WebFetch, WebSearch, LineCount, SearchLastOutput, PrintLastOutput, NotebookEdit, GetDiagnostics, AskUserQuestion, SleepTimer, PrintToConsole, execute_tool dispatcher, permission modes, diff helpers, plus _register_builtins, tool_registry integration.
Classes (30)
class TestRead
↳ test_read_existing_file(self, tmp_path)
↳ test_read_nonexistent(self, tmp_path)
↳ test_read_directory(self, tmp_path)
↳ test_read_with_limit(self, tmp_path)
↳ test_read_with_offset(self, tmp_path)
↳ test_read_offset_beyond_eof(self, tmp_path)
↳ test_read_empty_file(self, tmp_path)
↳ test_read_large_file(self, tmp_path)
Files over 10MB use chunked reading.
↳ test_read_unicode(self, tmp_path)
↳ test_read_with_expanduser(self, tmp_path, monkeypatch)
Test that ~ is expanded properly.
↳ test_file_header_format(self, tmp_path)
class TestLineCount
↳ test_basic(self, tmp_path)
↳ test_empty_file(self, tmp_path)
↳ test_nonexistent(self, tmp_path)
class TestWrite
↳ test_new_file(self, tmp_path)
↳ test_update_file(self, tmp_path)
↳ test_no_changes(self, tmp_path)
↳ test_creates_parent_dirs(self, tmp_path)
↳ test_diff_output(self, tmp_path)
↳ test_windows_crlf_handled(self, tmp_path)
class TestEdit
↳ test_basic_replace(self, tmp_path)
↳ test_nonexistent_file(self, tmp_path)
↳ test_not_found(self, tmp_path)
↳ test_multiple_occurrences(self, tmp_path)
↳ test_replace_all(self, tmp_path)
↳ test_crlf_file(self, tmp_path)
↳ test_exact_match_required(self, tmp_path)
↳ test_diff_generated(self, tmp_path)
class TestDiffHelpers
↳ test_generate_unified_diff(self)
↳ test_generate_unified_diff_no_change(self)
↳ test_maybe_truncate_diff_short(self)
↳ test_maybe_truncate_diff_long(self)
class TestIsSafeBash
↳ test_safe_commands(self, cmd)
↳ test_unsafe_commands(self, cmd)
class TestBash
↳ test_basic_command(self, mock_popen)
↳ test_with_stderr(self, mock_popen)
↳ test_timeout(self, mock_popen)
↳ test_no_output(self, mock_popen)
↳ test_exception(self, mock_popen)
class TestGlob
↳ test_basic_glob(self, tmp_path)
↳ test_recursive_glob(self, tmp_path)
↳ test_no_matches(self, tmp_path)
↳ test_default_cwd(self, tmp_path, monkeypatch)
class TestGrep
↳ test_files_with_matches(self, mock_run, mock_has_rg)
↳ test_content_mode(self, mock_run, mock_has_rg)
↳ test_count_mode(self, mock_run, mock_has_rg)
↳ test_no_matches(self, mock_run, mock_has_rg)
↳ test_error(self, mock_run, mock_has_rg)
↳ test_case_insensitive(self, mock_run, mock_has_rg)
↳ test_fallback_to_grep(self, mock_run, mock_has_rg)
class TestWebFetch
↳ test_fetch_url(self, mock_get)
↳ test_fetch_non_html(self, mock_get)
↳ test_fetch_file_protocol(self, tmp_path)
↳ test_fetch_nonexistent_file(self, tmp_path)
↳ test_fetch_error(self, mock_get)
↳ test_truncation(self, mock_get)
class TestWebSearch
↳ test_duckduckgo_html(self, mock_soup, mock_post)
↳ test_http_error(self, mock_post)
↳ test_brave_fallback(self)
↳ test_202_challenge_fallback(self, mock_post)
class TestBraveSearch
↳ test_success(self, mock_get)
↳ test_api_error(self, mock_get)
↳ test_no_results(self, mock_get)
class TestSearchAndPrintLastOutput
↳ _create_last_output(self, tmp_path, content)
Create a fake last_tool_output.txt in a temp location.
↳ test_search_no_file(self, tmp_path, monkeypatch)
↳ test_search_summary_mode(self, tmp_path, monkeypatch)
↳ test_search_pattern_mode(self, tmp_path, monkeypatch)
↳ test_search_no_matches(self, tmp_path, monkeypatch)
↳ test_search_invalid_regex(self, tmp_path, monkeypatch)
↳ test_print_last_output_no_file(self, tmp_path, monkeypatch)
↳ test_print_last_output_empty(self, tmp_path, monkeypatch)
↳ test_print_last_output_with_content(self, tmp_path, monkeypatch)
class TestNotebookEdit
↳ _make_nb(self, tmp_path, cells = None)
↳ test_replace_cell(self, tmp_path)
↳ test_replace_no_cell_id(self, tmp_path)
↳ test_insert_cell(self, tmp_path)
↳ test_insert_requires_cell_type(self, tmp_path)
↳ test_delete_cell(self, tmp_path)
↳ test_nonexistent_notebook(self, tmp_path)
↳ test_invalid_extension(self, tmp_path)
↳ test_invalid_json(self, tmp_path)
↳ test_cell_not_found(self, tmp_path)
↳ test_cell_n_shorthand(self, tmp_path)
↳ test_unknown_edit_mode(self, tmp_path)
↳ test_insert_at_beginning_no_cell_id(self, tmp_path)
class TestParseCellId
↳ test_valid(self)
↳ test_invalid(self)
class TestGetDiagnostics
↳ test_file_not_found(self)
↳ test_unknown_language(self, tmp_path)
↳ test_python_pyright(self, mock_rq, tmp_path)
↳ test_python_no_tools(self, mock_rq, tmp_path)
↳ test_detect_language(self)
↳ test_shellscript_shellcheck(self, mock_rq, tmp_path)
↳ test_typescript_tsc(self, mock_rq, tmp_path)
class TestRunQuietly
↳ test_success(self, mock_run)
↳ test_command_not_found(self, mock_run)
↳ test_timeout(self, mock_run)
class TestAskUserQuestion
↳ test_adds_to_pending(self)
↳ test_with_options(self)
↳ test_timeout_in_background_thread(self)
class TestSleepTimer
↳ test_no_callback(self)
↳ test_schedules_timer(self)
↳ test_timer_fires(self)
class TestPrintToConsole
↳ test_basic_content(self)
↳ test_with_style(self)
↳ test_with_prefix(self)
↳ test_info_style(self)
↳ test_warning_style(self)
↳ test_error_style(self)
↳ test_line_extraction(self, tmp_path)
↳ test_file_path(self, tmp_path)
↳ test_nonexistent_file(self, tmp_path)
↳ test_invalid_line_range(self)
↳ test_normal_style_no_prefix(self)
class TestToolSchemas
↳ test_all_have_name(self)
↳ test_all_have_description(self)
↳ test_all_have_input_schema(self)
↳ test_names_unique(self)
↳ test_tool_in_schemas(self, tool_name)
class TestExecuteToolDispatcher
↳ test_accept_all_no_check(self, tmp_path)
accept-all mode never asks permission.
↳ test_manual_rejected(self, tmp_path)
manual mode with rejecting ask_permission.
↳ test_manual_accepted(self, tmp_path)
↳ test_auto_mode(self, tmp_path)
auto mode (headless default) allows everything.
↳ test_bash_safe_auto(self)
Safe bash commands are auto-allowed.
↳ test_bash_unsafe_manual_rejected(self)
Unsafe bash in manual mode requires permission.
↳ test_unknown_tool(self)
↳ test_edit_permission(self, tmp_path)
↳ test_notebook_edit_permission(self, tmp_path)
class TestRegisterBuiltins
↳ test_tools_registered(self)
↳ test_read_only_flags(self)
↳ test_write_tools_not_read_only(self)
↳ test_print_to_console_display_only(self)
class TestCleanHtml
↳ test_basic(self)
↳ test_strips_style(self)
↳ test_empty_html(self)
↳ test_fallback_on_error(self)
If BeautifulSoup fails, falls back to raw truncated.
↳ test_beautifulsoup_import_error_fallback(self)
class TestLibreTranslateHost
↳ test_wsl_detected(self, mock_read, mock_exists)
↳ test_default(self)
class TestWinToPosix
↳ test_gitbash(self)
↳ test_wsl(self)
↳ test_no_match(self)
↳ test_forward_slash(self)
class TestKillProcTree
↳ test_unix_kill(self, mock_killpg)
↳ test_windows_kill(self, mock_run)
↳ test_unix_fallback(self, mock_kill, mock_killpg)
class TestFindWindowsBash
↳ test_bash_in_path(self, mock_which)
↳ test_git_default_location(self, mock_exists, mock_which)
↳ test_no_bash(self, mock_which)
class TestFindShellByType
↳ test_gitbash(self, mock_exists, mock_which)
↳ test_wsl(self, mock_exists, mock_which)
↳ test_unknown_shell_type(self, mock_which)
class TestEdgeCases
↳ test_ask_lock_is_mutex(self)
↳ test_pending_questions_is_list(self)
↳ test_tool_schemas_count(self)
Ensure we have expected number of tool schemas.
Imports
__future__json os pathlib pytest subprocess sys threading time tools unittest.mock
▶New folder/tools/__init__.py 65 LOC
Advanced tools package for Dulus RTK. This package provides 4 modules of advanced development tools: - git_tools: Git operations (diff, blame, log, branch, status) - code_analysis: Static code analysis (metrics, dead code, structure, compare) - dependency_mapper: Dependency mapping (imports, cycles, graphs, orphans) - profiler: Performance profiling (tool timing, session, memory, tokens) Usage: from tools import register_all_tools register_all_tools() # Regi
Functions (5)
def register_git_tools()
Register only the Git tools (GitDiff, GitBlame, GitLog, GitBranch, GitStatus).
def register_code_analysis_tools()
Register only the code analysis tools (CodeMetrics, FindDeadCode, CodeStructure, CompareFiles).
def register_dependency_mapper_tools()
Register only the dependency mapping tools (MapImports, FindCircularDeps, ModuleGraph, FindOrphans).
def register_profiler_tools()
Register only the profiler tools (ProfileTool, ProfileSession, MemorySnapshot, TokenUsage).
def register_all_tools()
Register all 17 advanced tools with the Dulus RTK tool registry. Returns: Total number of tools registered.
Imports
os tools.code_analysis tools.dependency_mapper tools.git_tools tools.profiler
▶New folder/tools/code_analysis.py 765 LOC
Code analysis tools module for Dulus RTK. Provides static analysis capabilities: code metrics, dead code detection, code structure visualization, and file comparison. Uses the Python ast module for parsing source files.
Functions (11)
def _read_file_safe(file_path: str)
Read a file safely, returning None on error. Args: file_path: Absolute path to the file. Returns: File contents as string, or None if file cannot be read.
def _parse_ast(source: str, filename: str = '')
Parse source code into an AST. Args: source: Python source code. filename: Filename for error reporting. Returns: AST tree or None if parsing fails.
def _count_sloc(source: str)
Count source lines of code (non-blank, non-comment). Args: source: File contents. Returns: Number of significant lines.
def _get_node_name(node: ast.AST)
Extract name from an AST node if it has one. Args: node: AST node. Returns: Name string or empty string.
def _code_metrics(params: Dict[str, Any], config: Dict[str, Any])
Calculate code metrics for a Python file.
def _find_dead_code(params: Dict[str, Any], config: Dict[str, Any])
Find potentially unused imports and unreferenced functions in a Python file.
def _code_structure(params: Dict[str, Any], config: Dict[str, Any])
Display the structure (classes, functions, imports) of a Python file.
def _format_args(args: ast.arguments, skip_first: bool = False)
Format function arguments into a string. Args: args: ast.arguments node. skip_first: If True, skip the first positional argument (for methods). Returns: Comma-separated argument string.
def _format_expr(node: ast.AST)
Try to format an AST expression node as a string. Args: node: AST expression node. Returns: String representation of the expression.
def _compare_files(params: Dict[str, Any], config: Dict[str, Any])
Compare two files and show a unified diff.
def register_all()
Register all code analysis tools with the tool registry.
Imports
ast difflib pathlib re tool_registry typing
▶New folder/tools/dependency_mapper.py 722 LOC
Dependency mapping tools module for Dulus RTK. Provides dependency analysis for Python projects: import mapping, circular dependency detection, Mermaid graph generation, and orphan finding. Uses the Python ast module for parsing and filesystem traversal.
Functions (11)
def _is_python_file(path: Path)
Check if path is a Python file. Args: path: Path to check. Returns: True if it's a .py file.
def _find_python_files(project_path: str)
Find all Python files under a project path. Args: project_path: Root directory to search. Returns: List of Path objects for .py files, sorted.
def _parse_imports(file_path: Path)
Parse a Python file and extract all imports. Args: file_path: Path to the Python file. Returns: List of (import_type, import_name, line_number) tuples. import_type is 'import', 'from', or 'relative'.
def _module_name_from_path(file_path: Path, project_root: Path)
Convert a file path to a module name relative to the project root. Args: file_path: Absolute path to the Python file. project_root: Project root directory. Returns: Dot-separated module name.
def _resolve_relative_import(importer_path: Path, project_root: Path, level: int, module: str)
Resolve a relative import to a module name. Args: importer_path: Path of the file doing the import. project_root: Project root directory. level: Number of dots in relative import. module: Module part after the dots. Returns: Resolved module name or None if cannot resolve.
def _build_dependency_graph(project_path: str)
Build a complete dependency graph for a Python project. Args: project_path: Root directory of the project. Returns: Dict mapping module_name -> { 'file': Path, 'imports': List[(type, name, line)], 'imported_by': List[str], }
def _map_imports(params: Dict[str, Any], config: Dict[str, Any])
Map all imports across a Python project.
def _find_circular_deps(params: Dict[str, Any], config: Dict[str, Any])
Detect circular dependencies in a Python project.
def _module_graph(params: Dict[str, Any], config: Dict[str, Any])
Generate a Mermaid diagram of module dependencies.
def _find_orphans(params: Dict[str, Any], config: Dict[str, Any])
Find modules that are not imported by any other module in the project.
def register_all()
Register all dependency mapping tools with the tool registry.
Imports
ast os pathlib tool_registry typing
▶New folder/tools/git_tools.py 664 LOC
Git tools module for Dulus RTK. Provides advanced Git operations: diff, blame, log, branch management, and status. All tools use subprocess to execute git commands and return formatted output.
Functions (10)
def _run_git(*args)
Run a git command and return (returncode, stdout, stderr). Args: *args: Git command arguments (e.g. "diff", "HEAD~1", "--stat"). cwd: Working directory. If None, uses the current working directory. timeout: Maximum seconds to wait for the command. Returns: Tuple of (returncode, std
def _resolve_cwd(params: Dict[str, Any])
Resolve the working directory from params or detect git root. Args: params: Tool parameters dict, may contain 'path' key. Returns: Absolute path to use as cwd for git commands.
def _is_git_repo(cwd: str)
Check if cwd is inside a git repository. Args: cwd: Directory to check. Returns: True if inside a git repo, False otherwise.
def _git_root(cwd: str)
Get the git repository root. Args: cwd: Directory inside the repo. Returns: Absolute path to the repository root.
def _git_diff(params: Dict[str, Any], config: Dict[str, Any])
Execute git diff between branches, commits, or working tree.
def _git_blame(params: Dict[str, Any], config: Dict[str, Any])
Execute git blame for a specific file and optional line range.
def _git_log(params: Dict[str, Any], config: Dict[str, Any])
Execute git log with various formatting options.
def _git_branch(params: Dict[str, Any], config: Dict[str, Any])
List, create, or delete branches.
def _git_status(params: Dict[str, Any], config: Dict[str, Any])
Show detailed git status with staged/modified/untracked files.
def register_all()
Register all Git tools with the tool registry.
Imports
os pathlib subprocess tool_registry typing
▶New folder/tools/profiler.py 647 LOC
Profiler tools module for Dulus RTK. Provides performance profiling: tool execution timing, session metrics, memory snapshots, and token usage analysis. Uses time.perf_counter for timing and psutil (with graceful fallback) for memory.
Classes (1)
class _TurnMetrics
Metrics for a single turn in the session.
Functions (7)
def _ensure_session()
Initialize session tracking if not already started.
def _get_memory_info()
Get current memory usage information. Returns: Dict with memory stats (with graceful fallback if psutil unavailable).
def _profile_tool(params: Dict[str, Any], config: Dict[str, Any])
Measure execution time of a specific tool or callable.
def _profile_session(params: Dict[str, Any], config: Dict[str, Any])
Show profiling data for the current session.
def _memory_snapshot(params: Dict[str, Any], config: Dict[str, Any])
Take a detailed snapshot of current memory usage.
def _token_usage(params: Dict[str, Any], config: Dict[str, Any])
Analyze token usage patterns from session data.
def register_all()
Register all profiler tools with the tool registry.
Imports
dataclasses json os pathlib sys time tool_registry typing
▶New folder/tools/test_code_analysis.py 360 LOC
Tests for the code_analysis module. Covers CodeMetrics, FindDeadCode, CodeStructure, and CompareFiles. Uses temporary Python files with known content for deterministic tests.
Classes (5)
class TestCodeMetrics
Test the CodeMetrics tool.
↳ setUp(self)
↳ tearDown(self)
↳ test_code_metrics_runs(self)
CodeMetrics produces output for a valid file.
↳ test_code_metrics_counts(self)
CodeMetrics counts classes and functions correctly.
↳ test_code_metrics_imports(self)
CodeMetrics lists imports.
↳ test_code_metrics_bad_file(self)
CodeMetrics handles missing file.
↳ test_code_metrics_syntax_error(self)
CodeMetrics handles file with syntax errors.
↳ test_code_metrics_complexity(self)
CodeMetrics reports complexity.
class TestFindDeadCode
Test the FindDeadCode tool.
↳ setUp(self)
↳ tearDown(self)
↳ test_find_dead_code_runs(self)
FindDeadCode produces output.
↳ test_find_dead_code_finds_unused_imports(self)
FindDeadCode detects unused imports.
↳ test_find_dead_code_finds_unused_function(self)
FindDeadCode detects unused_function.
↳ test_find_dead_code_finds_unused_class(self)
FindDeadCode detects UnusedClass.
↳ test_find_dead_code_no_false_positives(self)
FindDeadCode does not flag used names.
↳ test_find_dead_code_bad_file(self)
FindDeadCode handles missing file.
class TestCodeStructure
Test the CodeStructure tool.
↳ setUp(self)
↳ tearDown(self)
↳ test_code_structure_runs(self)
CodeStructure produces output.
↳ test_code_structure_imports_section(self)
CodeStructure shows imports.
↳ test_code_structure_functions_section(self)
CodeStructure shows functions.
↳ test_code_structure_classes_section(self)
CodeStructure shows classes and methods.
↳ test_code_structure_line_numbers(self)
CodeStructure shows line numbers by default.
↳ test_code_structure_no_line_numbers(self)
CodeStructure can hide line numbers.
↳ test_code_structure_variables(self)
CodeStructure shows module-level variables.
↳ test_code_structure_bad_file(self)
CodeStructure handles missing file.
class TestCompareFiles
Test the CompareFiles tool.
↳ setUp(self)
↳ tearDown(self)
↳ test_compare_files_identical(self)
CompareFiles reports identical files.
↳ test_compare_files_different(self)
CompareFiles shows diff for different files.
↳ test_compare_files_missing(self)
CompareFiles handles missing files.
↳ test_compare_files_with_labels(self)
CompareFiles uses custom labels.
↳ test_compare_files_stats(self)
CompareFiles includes change statistics.
class TestToolRegistration
Verify all code analysis tools are registered.
↳ setUp(self)
↳ test_all_tools_registered(self)
All 4 code analysis tools are registered.
↳ test_tools_read_only(self)
All code analysis tools are read-only.
↳ test_tools_concurrent_safe(self)
All code analysis tools are concurrent-safe.
Imports
os pathlib tempfile tool_registry tools.code_analysis unittest
▶New folder/tools/test_dependency_mapper.py 301 LOC
Tests for the dependency_mapper module. Covers MapImports, FindCircularDeps, ModuleGraph, and FindOrphans. Uses a temporary project structure with known import patterns.
Classes (6)
class TestHelpers
Test internal helper functions.
↳ setUp(self)
↳ tearDown(self)
↳ test_find_python_files(self)
_find_python_files finds all .py files.
↳ test_parse_imports(self)
_parse_imports extracts imports correctly.
↳ test_parse_imports_utils(self)
_parse_imports finds absolute imports.
↳ test_module_name_from_path(self)
_module_name_from_path converts path to dotted name.
↳ test_build_dependency_graph(self)
_build_dependency_graph builds correct graph.
class TestMapImports
Test the MapImports tool.
↳ setUp(self)
↳ tearDown(self)
↳ test_map_imports_runs(self)
MapImports produces output.
↳ test_map_imports_counts_files(self)
MapImports reports correct file count.
↳ test_map_imports_external(self)
MapImports with show_external includes external imports.
↳ test_map_imports_invalid_path(self)
MapImports handles invalid path.
↳ test_map_imports_empty_project(self)
MapImports handles empty project.
class TestFindCircularDeps
Test the FindCircularDeps tool.
↳ setUp(self)
↳ tearDown(self)
↳ test_find_circular_deps_runs(self)
FindCircularDeps produces output.
↳ test_find_circular_deps_finds_cycle(self)
FindCircularDeps detects circular_a <-> circular_b cycle.
↳ test_find_circular_deps_invalid_path(self)
FindCircularDeps handles invalid path.
↳ test_find_circular_deps_no_cycles(self)
FindCircularDeps reports no cycles for acyclic project.
class TestModuleGraph
Test the ModuleGraph tool.
↳ setUp(self)
↳ tearDown(self)
↳ test_module_graph_runs(self)
ModuleGraph produces output.
↳ test_module_graph_mermaid_syntax(self)
ModuleGraph output contains valid mermaid start.
↳ test_module_graph_direction(self)
ModuleGraph respects direction parameter.
↳ test_module_graph_invalid_path(self)
ModuleGraph handles invalid path.
↳ test_module_graph_display_only(self)
ModuleGraph is marked as display-only.
class TestFindOrphans
Test the FindOrphans tool.
↳ setUp(self)
↳ tearDown(self)
↳ test_find_orphans_runs(self)
FindOrphans produces output.
↳ test_find_orphans_finds_orphan(self)
FindOrphans detects orphan.py.
↳ test_find_orphans_shows_connected(self)
FindOrphans lists connected modules.
↳ test_find_orphans_invalid_path(self)
FindOrphans handles invalid path.
↳ test_find_orphans_excludes_special(self)
FindOrphans excludes __init__.py by default.
class TestToolRegistration
Verify all dependency mapper tools are registered.
↳ setUp(self)
↳ test_all_tools_registered(self)
All 4 dependency mapper tools are registered.
↳ test_tools_read_only(self)
All dependency mapper tools are read-only.
Imports
os pathlib tempfile tool_registry tools.dependency_mapper unittest
▶New folder/tools/test_git_tools.py 267 LOC
Tests for the git_tools module. Covers GitDiff, GitBlame, GitLog, GitBranch, and GitStatus. Uses a temporary git repository for realistic tests.
Classes (3)
class TestGitHelpers
Test internal helper functions.
↳ test_run_git_version(self)
Test that _run_git can execute a simple command.
↳ test_is_git_repo_false(self)
test_is_git_repo returns False outside a repo.
↳ test_git_root(self)
test_git_root returns the repo root.
class TestGitToolsInRepo
Test all git tools inside a real temporary repository.
↳ setUp(self)
Create a temp git repo with some commits.
↳ tearDown(self)
Clean up.
↳ test_git_diff_stat(self)
GitDiff with stat_only shows summary.
↳ test_git_diff_no_repo(self)
GitDiff returns error outside a repo.
↳ test_git_diff_full(self)
GitDiff full diff between branches.
↳ test_git_blame(self)
GitBlame on a file.
↳ test_git_blame_line(self)
GitBlame on a specific line.
↳ test_git_blame_no_file(self)
GitBlame without file_path errors.
↳ test_git_log(self)
GitLog shows commits.
↳ test_git_log_branch(self)
GitLog on a specific branch.
↳ test_git_log_no_repo(self)
GitLog outside repo returns error.
↳ test_git_branch_list(self)
GitBranch list shows branches.
↳ test_git_branch_create(self)
GitBranch create makes a new branch.
↳ test_git_branch_delete(self)
GitBranch delete removes a branch.
↳ test_git_branch_switch(self)
GitBranch switch changes branch.
↳ test_git_branch_invalid_op(self)
GitBranch with unknown operation errors.
↳ test_git_status_clean(self)
GitStatus on clean repo.
↳ test_git_status_with_changes(self)
GitStatus with modified file.
↳ test_git_status_short(self)
GitStatus short format.
↳ test_git_status_no_repo(self)
GitStatus outside repo returns error.
class TestGitToolSchemas
Verify all tool schemas are well-formed.
↳ setUp(self)
↳ test_all_tools_registered(self)
All 5 git tools are registered.
↳ test_git_diff_schema(self)
GitDiff schema is valid.
↳ test_git_branch_schema(self)
GitBranch schema has correct enum.
↳ test_git_tools_read_only(self)
Most git tools are read-only; branch is not.
Imports
os pathlib subprocess tempfile tool_registry tools.git_tools unittest
▶New folder/tools/test_profiler.py 294 LOC
Tests for the profiler module. Covers ProfileTool, ProfileSession, MemorySnapshot, and TokenUsage. Tests timing, memory tracking, and token analysis functionality.
Classes (6)
class TestMemoryHelpers
Test internal memory helper functions.
↳ test_get_memory_info_returns_dict(self)
_get_memory_info returns a dict with expected keys.
↳ test_get_memory_info_non_negative(self)
Memory values are non-negative.
class TestProfileTool
Test the ProfileTool.
↳ setUp(self)
↳ test_profile_tool_runs(self)
ProfileTool produces output for a valid tool.
↳ test_profile_tool_invalid_tool(self)
ProfileTool handles nonexistent tool.
↳ test_profile_tool_no_name(self)
ProfileTool requires tool_name.
↳ test_profile_tool_statistics(self)
ProfileTool reports timing statistics.
↳ test_profile_tool_histogram(self)
ProfileTool includes histogram for sufficient iterations.
↳ test_profile_tool_max_iterations(self)
ProfileTool caps iterations at 100.
class TestProfileSession
Test the ProfileSession tool.
↳ setUp(self)
↳ test_profile_session_runs(self)
ProfileSession produces output.
↳ test_profile_session_memory_section(self)
ProfileSession includes memory info.
↳ test_profile_session_python_info(self)
ProfileSession includes Python version info.
↳ test_profile_session_turn_count(self)
ProfileSession shows turn count from config.
↳ test_profile_session_registered_tools(self)
ProfileSession lists registered tools.
class TestMemorySnapshot
Test the MemorySnapshot tool.
↳ setUp(self)
↳ test_memory_snapshot_runs(self)
MemorySnapshot produces output.
↳ test_memory_snapshot_rss(self)
MemorySnapshot includes RSS.
↳ test_memory_snapshot_python_objects(self)
MemorySnapshot includes Python object count.
↳ test_memory_snapshot_detailed(self)
MemorySnapshot detailed mode shows more info.
↳ test_memory_snapshot_timestamp(self)
MemorySnapshot includes timestamp.
class TestTokenUsage
Test the TokenUsage tool.
↳ setUp(self)
↳ test_token_usage_no_data(self)
TokenUsage handles missing token data.
↳ test_token_usage_with_config(self)
TokenUsage reads from _token_usage config.
↳ test_token_usage_cost_estimate(self)
TokenUsage provides cost estimation.
↳ test_token_usage_provider_info(self)
TokenUsage displays provider/model.
↳ test_token_usage_from_last_response(self)
TokenUsage extracts from _last_response.
class TestToolRegistration
Verify all profiler tools are registered.
↳ setUp(self)
↳ test_all_tools_registered(self)
All 4 profiler tools are registered.
↳ test_tools_read_only(self)
All profiler tools are read-only.
↳ test_tools_not_display_only(self)
Profiler tools are not display-only (except none).
Functions (1)
def _dummy_tool_func(params: Dict[str, Any], config: Dict[str, Any])
A dummy tool that does minimal work.
Imports
os pathlib tempfile time tool_registry tools.profiler unittest unittest.mock
▶offload_helper.py 183 LOC
Offload Helper - Reemplazo para TmuxOffload Funciona con las herramientas tmux que sí funcionan
Classes (1)
class TmuxJob
Representa un job ejecutado en tmux
↳ __init__(self, command: str)
↳ start(self)
Inicia el job en tmux detached. Retorna session ID.
↳ is_running(self)
Verifica si el job sigue corriendo
↳ capture(self, lines: int = 1000)
Captura el output del job
↳ kill(self)
Mata el job y la sesión tmux
↳ wait(self, timeout: Optional[float] = None, poll_interval: float = 0.5)
Espera a que termine el job. Retorna True si terminó, False si timeout.
Functions (3)
def offload(command: str)
Ejecuta un comando en tmux detached (fire-and-forget). Retorna el session ID para capturar después. Uso: session = offload("sleep 10 && echo listo") # ... más tarde ... tmux capture-pane -t <session>:0.0 -p
def offload_and_wait(command: str, timeout: Optional[float] = None)
Ejecuta comando y espera a que termine. Uso: result = offload_and_wait("sleep 5 && date", timeout=10) print(result['output']) # stdout del comando
def list_offloaded()
Lista todas las sesiones dulus activas
Imports
subprocess time typing uuid
▶plugin/__init__.py 22 LOC
Plugin system for dulus.
Imports
loader recommend store types
▶plugin/autoadapter.py 1521 LOC
Auto-Adapter: Static analysis + AI to generate manifests for external repos.
Functions (19)
def _sanitize_python_code(code: str)
Fix common JSON-to-Python spills like true/false/null.
def _analyze_repository(plugin_dir: Path | str, verbose: bool = False)
Scan the repository for structure, functions, and dependencies (no execution).
def _extract_exports(code: str)
Extract public functions and classes from Python code using AST.
def generate_plugin_files(plugin_dir: Path, safe_name: str, config: dict)
Use AI to generate plugin_tool.py and plugin.json based on analysis.
def _compile_check(plugin_dir: Path)
Hard syntax check on plugin_tool.py.
def _load_plugin_module(plugin_dir: Path, safe_name: str)
Import plugin_tool.py and return (module_or_None, error_or_empty).
def _smoke_test_tool(td: Any)
Run a single tool with minimal valid params, mirroring execute_tool()'s stdout/stderr capture. Many plugin tools `print()` their output instead of returning it, so we MUST capture stdout or we will wrongly report "empty".
def _build_todo_items(plugin_dir: Path, safe_name: str)
Derive a structured todo list directly from the generated tools. Each item: {title, verify, status} verify is one of: 'compile' | 'import' | 'exports' | ('smoke', tool_name)
def _write_todo_file(plugin_dir: Path, safe_name: str, items: list[dict])
def _mark_task(todo_path: Path, title: str, status: str)
status: 'done' (x) or 'fail' (still [ ] but with FAILED tag)
def _run_verification(plugin_dir: Path, safe_name: str, verify: Any)
Dispatch to the right verification routine.
def _read_relevant_sources(plugin_dir: Path, error_msg: str, max_chars: int = 6000)
Read actual source files from the plugin repo to give the fix AI real API context. Prioritizes files whose names appear in the error message, then __init__.py files.
def _attempt_fresh_start(plugin_dir: Path, safe_name: str, accumulated_errors: list[str], analysis: dict, config: dict)
Full rewrite of plugin_tool.py from scratch after repeated fix failures. Feeds all accumulated error history so the agent doesn't repeat the same mistakes.
def _attempt_fix(plugin_dir: Path, safe_name: str, task_title: str, error_msg: str, analysis: dict, config: dict, original_goal: str | None = None, state = None, generation_context: str = '')
Run a full tool-enabled agent turn to fix a failing task. The agent has Read/Write/Edit/Bash/Grep/WebSearch — same as normal Dulus. Reuses existing state if provided (for multi-attempt fixes), otherwise creates new state. Returns (success, state) so state can be reused for next attempt. Args:
def _run_adapter_worker(plugin_dir: Path, safe_name: str, analysis: dict, config: dict, generator_context: str = '')
Worker loop: derive todo from generated tools, verify each, fix failures. Returns True only if every required task passes. Args: generator_context: Context from generation phase (reasoning text) to help fix agent understand the library
def _remove_failed_tools(plugin_dir: Path, safe_name: str, failed_tool_names: list[str], verbose: bool = False)
Update plugin_tool.py to only include working tools in TOOL_DEFS and TOOL_SCHEMAS. Keeps all the original code, just updates the export lists.
def _update_plugin_json_tools(plugin_dir: Path, safe_name: str, working_tool_names: list[str])
Update plugin.json to reflect only the working tools.
def _validate_generated_tools(plugin_dir: Path, safe_name: str)
Backward-compat shim — runs the worker without fix attempts (no AI).
def autoadapt_if_needed(plugin_dir: Path, name: str, config: dict)
Main entry point: check if manifest is missing and try to generate it.
Imports
__future__ast common json memory.context memory.sessions os pathlib providers sys tools types typing
▶plugin/loader.py 156 LOC
Plugin loader: discover and load tools/skills/mcp from installed plugins.
Functions (8)
def scrub_any_type(obj: Any)
Recursively remove 'type': 'any' from schema dictionaries as it's not valid JSON Schema.
def load_all_plugins(scope: PluginScope | None = None)
Return enabled plugins (optionally filtered by scope).
def load_plugin_tools(scope: PluginScope | None = None)
Import tool modules from all enabled plugins and collect their TOOL_SCHEMAS. Returns combined list of tool schema dicts.
def reload_plugins(scope: PluginScope | None = None)
Reload all plugins and register their tools. Returns a dict with counts of what was reloaded.
def register_plugin_tools(scope: PluginScope | None = None)
Import tool modules from enabled plugins and register them into tool_registry. Returns number of tools registered.
def load_plugin_skills(scope: PluginScope | None = None)
Return paths to skill markdown files from enabled plugins.
def load_plugin_mcp_configs(scope: PluginScope | None = None)
Return mcp server configs contributed by enabled plugins.
def _import_plugin_module(entry: PluginEntry, module_name: str)
Dynamically import a module from a plugin directory.
Imports
__future__importlib.util pathlib store sys types typing
▶plugin/recommend.py 211 LOC
Plugin recommendation engine: match installed + marketplace plugins to context.
Classes (1)
class PluginRecommendation
Functions (5)
def _tokenize(text: str)
Lower-case word tokens from text.
def _score_against_context(entry: dict, context_tokens: set[str])
Return (score, reasons) for a marketplace entry vs context tokens.
def recommend_plugins(context: str, top_n: int = 5, include_installed: bool = False)
Given a natural-language context string (e.g. current task description or user message), return up to top_n plugin recommendations sorted by relevance. Args: context: Free-text description of the current task / need. top_n: Maximum number of recommendations. include_installed: If True,
def recommend_from_files(paths: list[Path], top_n: int = 5)
Recommend plugins based on the types of files in the current project.
def format_recommendations(recs: list[PluginRecommendation])
Imports
__future__dataclasses pathlib re store types typing
▶plugin/store.py 387 LOC
Plugin store: install/uninstall/enable/disable/update + config persistence.
Functions (21)
def _project_plugin_dir()
def _project_plugin_cfg()
def _read_cfg(cfg_path: Path)
def _write_cfg(cfg_path: Path, data: dict)
def _plugin_dir_for(scope: PluginScope)
def _plugin_cfg_for(scope: PluginScope)
def list_plugins(scope: PluginScope | None = None)
Return all installed plugins (optionally filtered by scope).
def get_plugin(name: str, scope: PluginScope | None = None)
def install_plugin(identifier: str, scope: PluginScope = PluginScope.USER, force: bool = False)
Install a plugin. identifier = 'name' | 'name@git_url' | 'name@local_path'. Returns (success, message).
def _is_git_url(source: str)
def _clone_plugin(url: str, dest: Path)
def _install_dependencies(deps: list[str], cwd: Path | None = None)
def _update_plugin_list_memory(scope: PluginScope)
def _save_entry(entry: PluginEntry)
def _remove_entry(name: str, scope: PluginScope)
def uninstall_plugin(name: str, scope: PluginScope | None = None, keep_data: bool = False)
def _set_enabled(name: str, scope: PluginScope | None, enabled: bool)
def enable_plugin(name: str, scope: PluginScope | None = None)
def disable_plugin(name: str, scope: PluginScope | None = None)
def disable_all_plugins(scope: PluginScope | None = None)
def update_plugin(name: str, scope: PluginScope | None = None)
Imports
__future__json os pathlib shutil stat subprocess sys types typing
▶plugin/types.py 147 LOC
Plugin system types: manifest, entry, scope.
Classes (3)
class PluginScope
class PluginManifest
Parsed from PLUGIN.md YAML frontmatter or plugin.json.
↳ from_dict(cls, data: dict)
↳ from_plugin_dir(cls, plugin_dir: Path)
Load manifest from a plugin directory (plugin.json or PLUGIN.md frontmatter).
↳ _from_md(cls, md_file: Path)
class PluginEntry
A plugin registered in the config store.
↳ qualified_name(self)
↳ to_dict(self)
↳ from_dict(cls, data: dict)
Functions (2)
def parse_plugin_identifier(identifier: str)
Parse 'name' or 'name@source'. Returns (name, source_or_None).
def sanitize_plugin_name(name: str)
Ensure plugin name is safe for use as directory name (alphanumeric + underscore).
Imports
__future__dataclasses enum pathlib re typing
▶providers.py 3030 LOC
Multi-provider support for Dulus. Supported providers: anthropic — Claude (claude-opus-4-6, claude-sonnet-4-6, ...) openai — GPT (gpt-4o, o3-mini, ...) gemini — Google Gemini (gemini-2.0-flash, gemini-1.5-pro, ...) kimi — Moonshot AI (kimi-k2.5, moonshot-v1-8k/32k/128k) kimi-code — Kimi Code (kimi-for-coding, membership API from kimi.com/code) qwen — Alibaba DashScope (qwen-max, qwen-plus, ...) zhipu — Zhipu GLM (glm-4, glm-4-plus, ...) deepseek — D
Classes (6)
class _ProviderRetry
Lightweight retry wrapper for provider streaming calls. Retries on: timeout, connection errors, 429 (rate limit), 5xx. Does NOT retry on: 4xx (client errors), auth failures.
↳ is_retryable(cls, exc: Exception)
Return True if the exception is worth retrying.
↳ sleep_for_attempt(cls, attempt: int)
Exponential backoff with full jitter.
↳ wrap_generator(cls, fn: Callable, *args, **kwargs)
Wrap a generator function with retry logic. Yields through the generator; if it raises a retryable exception, waits and retries up to MAX_RETRIES times.
class WebToolParser
Shared parser for prompt-based tool calls in XML format. Also supports auto-wrapping raw JSON tool calls if auto_wrap_json=True.
↳ __init__(self, auto_wrap_json: bool = False)
↳ parse_chunk(self, chunk: str)
Parse chunk, return display text and accumulate tool calls.
↳ flush(self)
Return any remaining text in the buffer.
class _DeepSeekPoWSolver
Lazy-initialized WASM PoW solver for DeepSeek web (sha3_wasm_bg).
↳ get(cls)
↳ __init__(self)
↳ _get_mem_array(self)
↳ _alloc_string(self, s: str)
↳ solve(self, challenge: str, salt: str, expire_at: int, difficulty: int)
class TextChunk
↳ __init__(self, text)
class ThinkingChunk
↳ __init__(self, text)
class AssistantTurn
Completed assistant turn with text + tool_calls + thinking.
↳ __init__(self, text, tool_calls, in_tokens, out_tokens, thinking = '', error = False)
Functions (34)
def _format_web_tool_manifest(tool_schemas: list, config: dict, messages: list)
Format tools as a prompt hint for web models. Only injects on first turn or if always_inject_tools is True.
def _consolidate_web_history(messages: list, manifest: str = '')
Consolidate history since last assistant turn into one prompt string. This ensures tool results and system notifications are correctly perceived by web-based models that take a single prompt string.
def detect_provider(model: str)
Return provider name for a model string. Supports 'provider/model' explicit format, or auto-detect by prefix.
def _claude_web_cookies_path(config: dict)
Return path to claude.ai cookies JSON file.
def _kimi_web_auth_path(config: dict)
Return path to kimi.com consumer auth JSON file.
def _gemini_web_auth_path(config: dict)
Return path to gemini.google.com consumer auth JSON file.
def _deepseek_web_auth_path(config: dict)
Return path to chat.deepseek.com consumer auth JSON file.
def _claude_web_org_id(cookies_data: dict, config: dict)
Extract org ID: try cookies → try API → fallback from config → hardcoded.
def _claude_web_headers(cookies_data: dict, referer: str = 'https://claude.ai/new')
Build HTTP headers for claude.ai requests.
def _claude_web_fetch_org_id(cookies_data: dict)
Call /api/organizations using requests.Session with harvested cookies.
def _claude_web_create_conversation(cookies_data: dict, org_id: str)
Create a new claude.ai chat conversation using requests.Session.
def stream_claude_web(cookies_file: str, model: str, system: str, messages: list, tool_schemas: list, config: dict)
Stream from claude.ai web using harvested browser cookies. Tool calling is prompt-based: tool manifest injected into the user message; <tool_call>...</tool_call> tags parsed from the response. Conversation context is maintained server-side via conversation_id.
def stream_claude_code(cookies_file: str, model: str, system: str, messages: list, tool_schemas: list, config: dict)
Stream from claude.ai/code remote-control session using harvested cookies. Endpoint: POST https://claude.ai/v1/sessions/{session_id}/events Payload: {"events": [{"type":"user","uuid":"...","session_id":"...","parent_tool_use_id":null,"message":{"role":"user","content":"..."}}]} Auth: same clau
def stream_kimi_web(auth_file: str, model: str, system: str, messages: list, tool_schemas: list, config: dict)
Stream from kimi.com consumer web using harvested gRPC-Web tokens.
def stream_gemini_web(auth_file: str, model: str, system: str, messages: list, tool_schemas: list, config: dict)
Stream from gemini.google.com using the fast REST API with user-provided headers. Uses the 'requests' library with the exact cookies and headers captured from the user's browser. The harvester requires the user to type 'DULUS' as the message so we can locate and replace it in the f.req payload.
def stream_deepseek_web(auth_file: str, model: str, system: str, messages: list, tool_schemas: list, config: dict)
Stream from chat.deepseek.com web using harvested browser session. DeepSeek's web UI uses a simple SSE (text/event-stream) API: POST https://chat.deepseek.com/api/v0/chat/completion Headers: Authorization: Bearer <token> Body: { model, messages, stream: true, chat_session_id? } The har
def bare_model(model: str)
Strip 'provider/' prefix if present.
def get_api_key(provider_name: str, config: dict)
def calc_cost(model: str, in_tok: int, out_tok: int)
def estimate_tokens_kimi(api_key: str, model: str, messages: list)
Estimate token count using Kimi's native API endpoint. Args: api_key: Moonshot API key model: Model name (e.g., "kimi-k2.5") messages: List of message dicts with "role" and "content" Returns: Estimated token count, or None if the request fails
def scrub_any_type(obj: Any)
Recursively remove 'type': 'any' from schema dictionaries as it's not valid JSON Schema.
def tools_to_openai(tool_schemas: list)
Convert Anthropic-style tool schemas to OpenAI function-calling format.
def messages_to_anthropic(messages: list)
Convert neutral messages → Anthropic API format.
def messages_to_openai(messages: list, ollama_native_images: bool = False)
Convert neutral messages → OpenAI API format. Also sanitizes orphan tool_calls — if an assistant message has tool_calls but the matching tool responses are missing (e.g. user interrupted mid-call), the tool_calls are stripped to avoid API rejection.
def friendly_api_error(exc: Exception)
Map common API exceptions to short, actionable hints for the user. Returns a single-line string suitable for streaming back to the REPL. Falls back to the raw exception message when no pattern matches.
def _thinking_level_from(value)
Coerce legacy bool/int thinking config into an int 0-4.
def stream_anthropic(api_key: str, model: str, system: str, messages: list, tool_schemas: list, config: dict)
Stream from Anthropic API. Yields TextChunk/ThinkingChunk, then AssistantTurn.
def stream_kimi(api_key: str, model: str, system: str, messages: list, tool_schemas: list, config: dict)
Stream from Kimi API using native HTTP requests. Yields TextChunk, then AssistantTurn. This is a native implementation using urllib.request instead of the OpenAI SDK, allowing direct comparison with the OpenAI-compatible version. Token estimation: 1. Input tokens: Estimados ANTES usando estimate_t
def stream_openai_compat(api_key: str, base_url: str, model: str, system: str, messages: list, tool_schemas: list, config: dict)
Stream from any OpenAI-compatible API. Yields TextChunk, then AssistantTurn.
def _flatten_tool_messages(messages: list)
Convert tool-call history to plain text for models without native tool support. Transforms: - assistant messages with tool_calls → text + inline <tool_call> representation - role:tool messages → role:user with [Tool Result] prefix This lets the model see the full conversation without need
def _build_prompt_tool_manifest(tool_schemas: list)
Build the text block injected into the system prompt for prompt-based tool calling.
def stream_ollama(base_url: str, model: str, system: str, messages: list, tool_schemas: list, config: dict)
def stream(model: str, system: str, messages: list, tool_schemas: list, config: dict)
Unified streaming entry point. Auto-detects provider from model string. Yields: TextChunk | ThinkingChunk | AssistantTurn All provider calls are wrapped with automatic retry on transient failures (timeouts, 429 rate-limit, 5xx server errors).
def list_ollama_models(base_url: str)
Fetch locally available model tags from Ollama server.
Imports
__future__functools json random re requests time typing urllib.parse urllib.request
▶skill/__init__.py 14 LOC
skill package — reusable prompt templates (skills).
Imports
executor loader
▶skill/builtin.py 100 LOC
Built-in skills that ship with dulus.
Functions (1)
def _register_builtins()
Imports
__future__loader
▶skill/clawhub.py 243 LOC
ClawHub + local Anthropic skill importer for Dulus. Sources: - LOCAL : ~/.claude/plugins/marketplaces/claude-plugins-official/ (Anthropic, on-disk) - COMPOSIO : ~/.claude/plugins/marketplaces/awesome-claude-skills/ (ComposioHQ, 100+) - CLAWHUB : https://clawhub.ai (community, 52k+ skills, via API)
Functions (10)
def list_local(query: Optional[str] = None)
Return all SKILL.md entries from local marketplaces (Anthropic + Composio).
def get_local(slug: str)
Find a local skill by its id (plugin/skill or external/plugin/skill).
def install_local(slug: str)
Copy a local Anthropic skill (SKILL.md + all support files) into ~/.dulus/skills/<name>/
def search_clawhub(query: str, limit: int = 10)
Search ClawHub for skills matching query. TODO: fill in real Convex endpoint once reversed.
def install_clawhub(slug: str)
Download a skill from ClawHub by slug and save to ~/.dulus/skills/. TODO: fill in real endpoint.
def list_installed(query: Optional[str] = None)
Return skills already saved in ~/.dulus/skills/.
def read_skill(name: str)
Return the body (no frontmatter) of an installed skill.
def _parse_frontmatter(text: str)
def _strip_frontmatter(text: str)
def _dulus_frontmatter(entry: dict)
Imports
__future__json pathlib re typing urllib.parse urllib.request
▶skill/executor.py 66 LOC
Skill execution: inline (current conversation) or forked (sub-agent).
Functions (3)
def execute_skill(skill: SkillDef, args: str, state, config: dict, system_prompt: str)
Execute a skill. If skill.context == "fork", runs as an isolated sub-agent and yields its events. Otherwise (inline), injects the rendered prompt into the current agent loop. Args: skill: SkillDef to execute args: raw argument string from user (after the trigger word) state: AgentState
def _execute_inline(message: str, state, config: dict, system_prompt: str)
Run skill prompt inline in the current conversation.
def _execute_forked(skill: SkillDef, message: str, config: dict, system_prompt: str)
Run skill as an isolated sub-agent (separate conversation context).
Imports
__future__loader typing
▶skill/loader.py 199 LOC
Skill loading: parse markdown files with YAML frontmatter into SkillDef objects.
Classes (1)
class SkillDef
Functions (7)
def _get_skill_paths()
def _parse_list_field(value: str)
Parse YAML-like list: ``[a, b, c]`` or ``"a, b, c"``.
def _parse_skill_file(path: Path, source: str = 'user')
Parse a markdown file with ``---`` frontmatter into a SkillDef. Frontmatter fields: name, description, triggers, tools / allowed-tools, when_to_use, argument-hint, arguments, model, user-invocable, context
def register_builtin_skill(skill: SkillDef)
def load_skills(include_builtins: bool = True)
Return skills from disk + builtins, deduplicated (project > user > builtin).
def find_skill(query: str)
Find a skill whose trigger matches the first word (or whole string) of query.
def substitute_arguments(prompt: str, args: str, arg_names: list[str])
Replace $ARGUMENTS (whole args string) and $ARG_NAME placeholders. Named args are positional: first word → first name, etc. Values are substituted literally; placeholder *names* are validated to avoid pathological replace() chains, but values are NOT shell-escaped — callers using the result in shel
Imports
__future__dataclasses pathlib re typing
▶skill/tools.py 110 LOC
Skill tool: lets the model invoke skills by name via tool call.
Functions (3)
def _skill_tool(params: dict, config: dict)
Execute a skill by name and return its output.
def _skill_list_tool(params: dict, config: dict)
def _register()
Imports
__future__loader tool_registry
▶skills.py 14 LOC
Backward-compatibility shim — real implementation is in skill/ package.
Imports
skill.executor skill.loader
▶startup_context.py 27 LOC
Startup Context Loader for Dulus This module automatically loads MemPalace context when Dulus starts
Functions (1)
def load_startup_context()
Load context at Dulus startup and display summary.
Imports
context_integration
▶subagent.py 11 LOC
Backward-compatibility shim — real implementation is in multi_agent/subagent.py.
Imports
multi_agent.subagent
▶task/__init__.py 12 LOC
Task system for dulus.
Imports
store types
▶task/store.py 199 LOC
Thread-safe task store: in-memory dict persisted to .dulus/tasks.json.
Functions (11)
def _tasks_file()
def _load()
def _save()
def _next_id()
Generate a short sequential numeric ID.
def create_task(subject: str, description: str, active_form: str = '', metadata: dict[str, Any] | None = None)
def get_task(task_id: str)
def list_tasks()
def update_task(task_id: str, subject: str | None = None, description: str | None = None, status: str | None = None, active_form: str | None = None, owner: str | None = None, add_blocks: list[str] | None = None, add_blocked_by: list[str] | None = None, metadata: dict[str, Any] | None = None)
Update a task. Returns (updated_task, list_of_updated_fields).
def delete_task(task_id: str)
def clear_all_tasks()
Remove all tasks (used in tests).
def reload_from_disk()
Force reload from disk (used in tests).
Imports
__future__datetime json pathlib threading types typing uuid
▶task/tools.py 265 LOC
Task tools: TaskCreate, TaskUpdate, TaskGet, TaskList — registered into tool_registry.
Functions (5)
def _task_create(subject: str, description: str, active_form: str = '', metadata: dict = None)
def _task_update(task_id: str, subject: str = None, description: str = None, status: str = None, active_form: str = None, owner: str = None, add_blocks: list = None, add_blocked_by: list = None, metadata: dict = None)
def _task_get(task_id: str)
def _task_list()
def _register()
Imports
__future__store tool_registry types
▶task/types.py 92 LOC
Task system types: Task dataclass, TaskStatus enum.
Classes (2)
class TaskStatus
class Task
↳ to_dict(self)
↳ from_dict(cls, data: dict)
↳ status_icon(self)
↳ one_line(self, resolved_ids: set[str] | None = None)
Imports
__future__dataclasses datetime enum typing
▶tests/__init__.py 0 LOC
▶tests/e2e_checkpoint.py 228 LOC
End-to-end checkpoint test: simulate a real user session.
Classes (1)
class AgentState
Functions (1)
def auto_snapshot(user_input)
Same logic as dulus.py auto-snapshot with throttle.
Imports
checkpoint checkpoint.hooks checkpoint.store dataclasses datetime os pathlib shutil sys tempfile uuid
▶tests/e2e_commands.py 191 LOC
End-to-end test for /init, /export, /copy, /status commands.
Classes (1)
class FakeState
Functions (2)
def test_commands()
def _run_tests(tmpdir)
Imports
__future__dataclasses json os pathlib shutil sys tempfile unittest.mock
▶tests/e2e_compact.py 193 LOC
Tests for /compact command and compaction enhancements.
Classes (1)
class FakeState
Functions (1)
def test_compact()
Imports
__future__dataclasses pathlib sys unittest.mock
▶tests/e2e_plan_mode.py 182 LOC
End-to-end test for plan mode.
Classes (1)
class FakeState
Functions (1)
def test_plan_mode()
Imports
__future__dataclasses os pathlib sys tempfile
▶tests/e2e_plan_tools.py 167 LOC
End-to-end test for EnterPlanMode / ExitPlanMode tools.
Functions (2)
def test_plan_tools()
def _run(tmpdir)
Imports
__future__os pathlib shutil sys tempfile
▶tests/test_checkpoint.py 458 LOC
Tests for the checkpoint system.
Classes (5)
class FakeState
class TestTypes
↳ test_file_backup_roundtrip(self)
↳ test_file_backup_none_filename(self)
↳ test_snapshot_roundtrip(self)
class TestStore
↳ test_track_file_edit_existing_file(self, tmp_home, tmp_path)
↳ test_track_file_edit_nonexistent(self, tmp_home, tmp_path)
↳ test_track_file_edit_large_file_skipped(self, tmp_home, tmp_path)
↳ test_make_snapshot_basic(self, tmp_home, tmp_path)
↳ test_make_snapshot_incremental(self, tmp_home, tmp_path)
↳ test_list_snapshots(self, tmp_home)
↳ test_get_snapshot(self, tmp_home)
↳ test_rewind_files(self, tmp_home, tmp_path)
↳ test_rewind_deletes_new_file(self, tmp_home, tmp_path)
↳ test_max_snapshots_sliding_window(self, tmp_home)
↳ test_files_changed_since(self, tmp_home, tmp_path)
↳ test_delete_session_checkpoints(self, tmp_home)
↳ test_cleanup_old_sessions(self, tmp_home)
class TestHooks
↳ test_set_session_and_tracking(self, tmp_home, tmp_path)
↳ test_reset_tracked(self, tmp_home, tmp_path)
↳ test_install_hooks_wraps_tools(self)
Verify install_hooks wraps Write/Edit/NotebookEdit without error.
class TestIntegration
↳ test_write_snapshot_rewind_cycle(self, tmp_home, tmp_path)
Simulate: write file → snapshot → modify → rewind → verify restored.
↳ test_initial_snapshot(self, tmp_home)
Initial snapshot should be id=1 with empty messages and prompt '(initial state)'.
↳ test_throttle_skips_when_no_changes(self, tmp_home)
Snapshot should be skipped when no files changed and message_index is same.
↳ test_throttle_creates_when_messages_grew(self, tmp_home)
Snapshot should be created when messages grew even without file changes.
↳ test_throttle_conversation_rewind_works(self, tmp_home)
After throttled snapshots, conversation rewind via message_index still works.
Functions (2)
def tmp_home(tmp_path)
Redirect ~/.dulus/checkpoints to a temp directory.
def reset_versions()
Reset file version counters between tests.
Imports
__future__dataclasses json os pathlib pytest shutil tempfile unittest.mock
▶tests/test_compaction.py 187 LOC
Tests for compaction.py — token estimation, context limits, snipping, split point.
Classes (4)
class TestEstimateTokens
↳ test_simple_messages(self)
↳ test_empty_messages(self)
↳ test_empty_content(self)
↳ test_tool_result_messages(self)
↳ test_structured_content(self)
Content that is a list of dicts (e.g. Anthropic tool_result blocks).
↳ test_with_tool_calls(self)
class TestGetContextLimit
↳ test_anthropic(self)
↳ test_gemini(self)
↳ test_deepseek(self)
↳ test_openai(self)
↳ test_qwen(self)
↳ test_unknown_model_fallback(self)
↳ test_explicit_provider_prefix(self)
class TestSnipOldToolResults
↳ test_old_tool_results_get_truncated(self)
↳ test_recent_tool_results_preserved(self)
↳ test_short_tool_results_not_touched(self)
↳ test_non_tool_messages_untouched(self)
class TestFindSplitPoint
↳ test_returns_reasonable_index(self)
↳ test_single_message(self)
↳ test_empty_messages(self)
↳ test_split_preserves_recent(self)
Imports
__future__compaction os sys
▶tests/test_diff_view.py 50 LOC
Functions (6)
def test_generate_unified_diff()
def test_generate_unified_diff_empty_old()
def test_edit_returns_diff(tmp_path)
def test_write_existing_returns_diff(tmp_path)
def test_write_new_file_no_diff(tmp_path)
def test_diff_truncation()
Imports
os pytest sys tempfile
▶tests/test_injection_fix.py 65 LOC
Functions (1)
def test_consolidation()
Imports
os providers sys
▶tests/test_license.py 208 LOC
Tests for Dulus license system.
Classes (5)
class TestLicenseValidation
↳ test_valid_pro_key(self)
↳ test_valid_enterprise_key(self)
↳ test_invalid_signature_wrong_secret(self)
↳ test_expired_key(self)
↳ test_free_tier_no_key(self)
↳ test_malformed_prefix(self)
↳ test_malformed_base64(self)
↳ test_payload_tampering_tier_changed(self)
Un atacante modifica el tier en el payload pero reusa la firma original.
↳ test_payload_tampering_expiry_extended(self)
Un atacante extiende la expiración pero reusa la firma original.
↳ test_expired_exact_boundary(self)
Key que expira exactamente AHORA debe ser inválida.
class TestFeatureGates
↳ test_free_limits(self)
↳ test_pro_limits(self)
↳ test_enterprise_limits(self)
↳ test_pro_vs_free_features(self)
class TestRevocation
↳ test_revoked_key_simulated(self)
Simulación de revocación: el manager no tiene revocación nativa, pero el servidor sí. Este test documenta el comportamiento esperado.
class TestCryptoConsistency
↳ test_manager_vs_server_signature_algorithm(self)
Manager y server deben usar el mismo algoritmo HMAC (raw secret).
↳ test_cross_validation_manager_to_server(self)
Una key generada por license_manager debe validar en license_server.
class TestMachineFingerprint
↳ test_machine_locked_key(self)
Cuando se implemente, una key generada para máquina A debe fallar en máquina B.
Imports
base64 json license_manager pathlib sys time unittest
▶tests/test_mcp.py 395 LOC
Tests for the MCP package (mcp/).
Classes (5)
class TestTypes
↳ test_server_config_from_dict_stdio(self)
↳ test_server_config_from_dict_sse(self)
↳ test_server_config_defaults(self)
↳ test_server_config_disabled(self)
↳ test_mcp_tool_schema(self)
↳ test_make_request(self)
↳ test_make_request_with_params(self)
↳ test_make_notification(self)
↳ test_init_params_structure(self)
class TestConfig
↳ test_load_empty(self, tmp_config)
↳ test_load_user_config(self, tmp_config)
↳ test_load_project_config_overrides_user(self, tmp_config, monkeypatch)
↳ test_add_server_to_user_config(self, tmp_config)
↳ test_remove_server_from_user_config(self, tmp_config)
↳ test_remove_nonexistent(self, tmp_config)
↳ test_multiple_servers(self, tmp_config)
class TestMCPClient
↳ _make_client(self, transport_mock)
↳ test_list_tools_empty(self)
↳ test_list_tools_parses_tool(self)
↳ test_list_tools_read_only_hint(self)
↳ test_list_tools_no_tools_capability(self)
↳ test_call_tool_success(self)
↳ test_call_tool_error_flag(self)
↳ test_call_tool_image_content(self)
↳ test_call_tool_not_connected(self)
↳ test_qualified_name_sanitized(self)
↳ test_status_line_connected(self)
↳ test_status_line_error(self)
class TestMCPManager
↳ test_add_server(self)
↳ test_call_tool_unknown_server(self)
↳ test_call_tool_invalid_name(self)
↳ test_all_tools_empty_when_disconnected(self)
↳ test_all_tools_from_connected_server(self)
↳ test_singleton(self)
class TestStdioTransportEcho
Use Python's own interpreter as a trivial echo MCP server.
↳ test_full_round_trip(self, tmp_path)
Functions (2)
def reset_manager(monkeypatch)
Each test gets a fresh MCPManager singleton.
def tmp_config(tmp_path, monkeypatch)
Redirect MCP config paths to tmp_path.
Imports
__future__json mcp.client mcp.config mcp.types pathlib pytest threading time unittest.mock
▶tests/test_memory.py 275 LOC
Tests for the memory package (memory/).
Classes (9)
class TestSaveAndLoad
↳ test_roundtrip(self)
↳ test_creates_file_on_disk(self)
↳ test_update_existing(self)
Save same name twice → only 1 entry with updated content.
↳ test_project_scope_stored_separately(self)
↳ test_load_index_all_combines_scopes(self)
class TestDelete
↳ test_delete_removes_file_and_index(self)
↳ test_delete_nonexistent_no_error(self)
↳ test_delete_from_project_scope(self)
class TestSearch
↳ test_search_by_keyword(self)
↳ test_search_case_insensitive(self)
↳ test_search_in_content(self)
↳ test_search_across_scopes(self)
class TestGetMemoryContext
↳ test_returns_index_text(self)
↳ test_empty_when_no_memories(self)
↳ test_project_memories_labelled(self)
class TestTruncation
↳ test_no_truncation_within_limits(self)
↳ test_line_truncation(self)
↳ test_byte_truncation(self)
class TestSlugify
↳ test_basic(self)
↳ test_special_chars(self)
↳ test_max_length(self)
class TestParseFrontmatter
↳ test_parse(self)
↳ test_no_frontmatter(self)
class TestScanAndAge
↳ test_scan_memory_dir(self)
↳ test_format_manifest(self)
↳ test_memory_age_days_today(self)
↳ test_memory_age_days_old(self)
↳ test_memory_age_str(self)
↳ test_freshness_text_fresh(self)
↳ test_freshness_text_stale(self)
class TestMemoryTypes
↳ test_types_list(self)
Functions (2)
def redirect_memory_dirs(tmp_path, monkeypatch)
Redirect user and project memory dirs to tmp_path for all tests.
def _make_entry(name = 'test note', description = 'a test', type_ = 'user', content = 'hello world', scope = 'user')
Imports
memory.context memory.scan memory.store memory.types pathlib pytest
▶tests/test_plugin.py 350 LOC
Tests for the plugin package (plugin/).
Classes (4)
class TestPluginTypes
↳ test_parse_simple(self)
↳ test_parse_with_source(self)
↳ test_sanitize_name(self)
↳ test_manifest_from_dict(self)
↳ test_manifest_defaults(self)
↳ test_manifest_from_plugin_dir_json(self, tmp_path, local_plugin)
↳ test_manifest_from_plugin_dir_md(self, tmp_path)
↳ test_manifest_missing(self, tmp_path)
↳ test_entry_to_dict_roundtrip(self, tmp_path)
↳ test_entry_qualified_name(self, tmp_path)
class TestPluginStore
↳ test_list_empty(self)
↳ test_install_local(self, local_plugin)
↳ test_install_creates_dir(self, local_plugin)
↳ test_install_no_source_error(self)
↳ test_install_duplicate(self, local_plugin)
↳ test_install_force(self, local_plugin)
↳ test_get_plugin(self, local_plugin)
↳ test_get_plugin_missing(self)
↳ test_uninstall(self, local_plugin)
↳ test_uninstall_not_found(self)
↳ test_enable_disable(self, local_plugin)
↳ test_disable_all(self, local_plugin, tmp_path)
↳ test_update_local_path_rejected(self, local_plugin)
↳ test_update_not_found(self)
↳ test_project_scope(self, local_plugin)
class TestPluginRecommend
↳ test_empty_context(self)
↳ test_git_context(self)
↳ test_python_lint_context(self)
↳ test_sql_context(self)
↳ test_top_n(self)
↳ test_sorted_by_score(self)
↳ test_recommend_from_files(self, tmp_path)
↳ test_format_recommendations(self)
↳ test_format_empty(self)
class TestAskUserQuestion
↳ test_drain_empty(self)
drain_pending_questions returns False when nothing pending.
↳ test_roundtrip_with_freetext(self)
Submit a question, simulate user typing 'yes', collect result.
↳ test_roundtrip_with_option_selection(self)
Select option 1 from a numbered list.
↳ test_tool_schema_registered(self)
AskUserQuestion must appear in TOOL_SCHEMAS.
Functions (2)
def tmp_plugin_paths(tmp_path, monkeypatch)
Redirect all plugin config paths to tmp_path.
def local_plugin(tmp_path)
Create a minimal local plugin directory.
Imports
__future__json pathlib plugin.recommend plugin.store plugin.types pytest shutil threading unittest.mock
▶tests/test_skills.py 234 LOC
Functions (23)
def skill_dir(tmp_path, monkeypatch)
Create a temp skill directory with sample skills and patch _get_skill_paths.
def test_parse_list_field_bracket()
def test_parse_list_field_plain()
def test_parse_list_field_single()
def test_parse_skill_file(skill_dir)
def test_parse_skill_file_review(skill_dir)
def test_parse_skill_file_invalid(tmp_path)
def test_parse_skill_file_no_name(tmp_path)
def test_parse_skill_file_context_fork(tmp_path)
def test_parse_skill_file_allowed_tools(tmp_path)
def test_load_skills(skill_dir)
def test_load_skills_empty_dir(tmp_path, monkeypatch)
def test_load_skills_nonexistent_dir(tmp_path, monkeypatch)
def test_load_skills_builtins_present(monkeypatch)
Without patching, builtins (commit, review) should be present.
def test_load_skills_project_overrides_builtin(tmp_path, monkeypatch)
A project skill with the same name overrides the builtin.
def test_find_skill_commit(skill_dir)
def test_find_skill_review(skill_dir)
def test_find_skill_review_pr(skill_dir)
def test_find_skill_nonexistent(skill_dir)
def test_substitute_arguments_placeholder()
def test_substitute_named_args(tmp_path)
def test_substitute_missing_arg()
def test_substitute_no_placeholders()
Imports
__future__pathlib pytest skill skill.loader
▶tests/test_subagent.py 136 LOC
Tests for the sub-agent system (subagent.py).
Classes (7)
class TestSpawnAndWait
↳ test_spawn_and_wait_completes(self, manager)
↳ test_spawn_returns_immediately(self, manager)
class TestListTasks
↳ test_list_tasks(self, manager)
class TestCancel
↳ test_cancel_running_task(self, slow_manager)
class TestDepthLimit
↳ test_spawn_at_max_depth_fails(self, manager)
class TestGetResult
↳ test_get_result_completed(self, manager)
↳ test_get_result_unknown_id(self, manager)
class TestExtractFinalText
↳ test_extracts_last_assistant(self)
↳ test_returns_none_for_empty(self)
↳ test_returns_none_no_assistant(self)
class TestWaitUnknown
↳ test_wait_unknown_returns_none(self, manager)
Functions (4)
def _make_mock_agent_run(sleep_per_iter = 0.05, iters = 3)
Return a mock _agent_run that simulates work and checks cancellation.
def _make_slow_mock(sleep_per_iter = 0.2, iters = 10)
Return a slow mock for cancellation testing.
def manager(monkeypatch)
Create a SubAgentManager with mocked _agent_run.
def slow_manager(monkeypatch)
Create a SubAgentManager with a slow mock for cancel testing.
Imports
multi_agent.subagent pytest threading time
▶tests/test_task.py 292 LOC
Tests for the task package (task/).
Classes (3)
class TestTaskTypes
↳ test_default_status(self)
↳ test_status_icon(self)
↳ test_to_dict_roundtrip(self)
↳ test_from_dict_unknown_status_defaults_pending(self)
↳ test_one_line_no_blockers(self)
↳ test_one_line_with_blockers(self)
↳ test_one_line_resolved_blockers_hidden(self)
class TestTaskStore
↳ test_create_returns_task(self)
↳ test_ids_are_sequential(self)
↳ test_get_returns_task(self)
↳ test_get_unknown_returns_none(self)
↳ test_list_returns_all(self)
↳ test_list_empty(self)
↳ test_update_status(self)
↳ test_update_subject_and_description(self)
↳ test_update_owner(self)
↳ test_update_no_changes_returns_empty_fields(self)
↳ test_update_unknown_task(self)
↳ test_update_add_blocks(self)
↳ test_update_add_blocked_by(self)
↳ test_update_metadata_merge(self)
↳ test_delete_removes_task(self)
↳ test_delete_unknown(self)
↳ test_persistence_round_trip(self, tmp_path)
Tasks saved to disk are re-loaded correctly.
↳ test_clear_all(self)
↳ test_thread_safety(self)
Concurrent creates should produce unique IDs.
class TestTaskToolFunctions
Test the string-returning functions used by the registered tools.
↳ test_task_create_tool(self)
↳ test_task_update_tool_status(self)
↳ test_task_update_tool_delete(self)
↳ test_task_update_not_found(self)
↳ test_task_get_tool(self)
↳ test_task_get_not_found(self)
↳ test_task_list_tool_empty(self)
↳ test_task_list_tool_multiple(self)
↳ test_task_list_hides_resolved_blockers(self)
↳ test_tool_schemas_registered(self)
All four task tools must be registered in tool_registry.
↳ test_tool_schemas_in_tool_schemas_list(self)
Task tool schemas are also present in TOOL_SCHEMAS for Claude's tool list.
Functions (1)
def isolated_store(tmp_path, monkeypatch)
Each test gets a fresh in-memory + on-disk task store.
Imports
__future__json pathlib pytest task task.store task.types threading
▶tests/test_telegram_buffer.py 60 LOC
Functions (2)
def test_telegram_buffer_pruning()
Test that old Telegram messages are pruned from output buffer.
def test_sanitize_text()
Test that sanitize_text removes surrogates but keeps valid text/emojis.
Imports
common input os sys
▶tests/test_tool_registry.py 160 LOC
Functions (12)
def _clean_registry()
Reset registry before each test.
def _make_echo_tool(name: str = 'echo', read_only: bool = False)
Helper to build a simple echo tool.
def test_register_and_get()
def test_get_unknown_returns_none()
def test_get_all_tools_empty()
def test_get_all_tools()
def test_get_tool_schemas()
def test_execute_tool()
def test_execute_unknown_tool()
def test_output_truncation()
def test_no_truncation_when_within_limit()
def test_duplicate_register_overwrites()
Imports
__future__pytest tool_registry
▶tests/test_voice.py 240 LOC
Tests for the voice/ package (no hardware required). All tests run without a microphone or STT library installed. They cover the pure-Python helpers: WAV wrapping, keyterm extraction, availability checks, and the REPL integration sentinel.
Classes (8)
class TestSplitIdentifier
↳ test_camel_case(self)
↳ test_kebab_case(self)
↳ test_snake_case(self)
↳ test_short_fragments_dropped(self)
↳ test_path_like(self)
class TestGetVoiceKeyterms
↳ test_returns_list(self)
↳ test_global_terms_present(self)
↳ test_max_length(self)
↳ test_deduplication(self)
↳ test_recent_files_passed(self)
class TestPcmToWav
↳ test_riff_header(self)
↳ test_data_chunk(self)
↳ test_roundtrip_length(self)
class TestKeytermsToPrompt
↳ test_empty(self)
↳ test_contains_terms(self)
↳ test_truncates_at_40(self)
class TestSttAvailability
↳ test_returns_tuple(self)
↳ test_backend_name_string(self)
↳ test_openai_api_available_when_key_set(self)
↳ test_unavailable_without_backends(self)
class TestRecorderAvailability
↳ test_returns_tuple(self)
↳ test_sounddevice_makes_available(self)
class TestVoiceInit
↳ test_check_voice_deps_returns_tuple(self)
↳ test_exports(self)
class TestReplVoiceIntegration
↳ test_voice_in_commands(self)
↳ test_voice_command_callable(self)
↳ test_handle_slash_voice_sentinel(self)
handle_slash('/voice ...') propagates __voice__ sentinel from cmd_voice.
↳ test_voice_status_no_crash(self, capsys)
'/voice status' should not raise even without audio hardware.
↳ test_voice_lang_set(self, capsys)
Functions (1)
def _make_pcm(n_samples: int = 1600)
Return silent int16 PCM (all zeros).
Imports
__future__pathlib pytest struct sys unittest.mock
▶tmux_offloader.py 177 LOC
TmuxOffloader - Wrapper alternativo a TmuxOffload Usa tmux directamente ya que TmuxOffload tiene bugs
Functions (6)
def generate_session_name(prefix = 'job')
Genera nombre único de sesión
def run_in_tmux(command, session_name = None, wait = False, timeout = None)
Ejecuta un comando en una sesión tmux detached. Args: command: Comando a ejecutar (string) session_name: Nombre de sesión (auto-generado si None) wait: Si True, espera a que termine y retorna output timeout: Segundos máximos de espera (si wait=True) Returns: Si wait=False: sess
def get_session_output(session_name)
Captura el output de una sesión tmux existente. Retorna el output o None si la sesión no existe.
def is_session_active(session_name)
Verifica si una sesión tmux sigue activa
def kill_session(session_name)
Mata una sesión tmux
def list_sessions()
Lista todas las sesiones tmux activas
Imports
pathlib random string subprocess time
▶tmux_tools.py 410 LOC
Tmux integration tools for Dulus. Gives the AI model direct control over tmux sessions: create panes, send commands, read output, and manage layouts. Auto-detected at startup — tools are only registered when tmux is available on the host.
Functions (17)
def _find_tmux()
Locate a tmux binary.
def tmux_available()
Return True if a tmux-compatible binary exists on the system.
def _safe(value: str)
Sanitize a tmux target/session name to prevent shell injection.
def _t(params: dict, key: str = 'target')
Build a -t flag from params, or empty string if absent.
def _run(cmd: str, timeout: int = 10)
Run a tmux command and return combined stdout+stderr. Replaces bare 'tmux' prefix with the detected binary path. Unsets nesting guards ($TMUX / $PSMUX_SESSION) so commands work from inside an existing session.
def _tmux_list_sessions(params: dict, config: dict)
def _tmux_new_session(params: dict, config: dict)
def _tmux_split_window(params: dict, config: dict)
def _tmux_send_keys(params: dict, config: dict)
def _tmux_capture_pane(params: dict, config: dict)
def _tmux_list_panes(params: dict, config: dict)
def _tmux_select_pane(params: dict, config: dict)
def _tmux_kill_pane(params: dict, config: dict)
def _tmux_new_window(params: dict, config: dict)
def _tmux_list_windows(params: dict, config: dict)
def _tmux_resize_pane(params: dict, config: dict)
def register_tmux_tools()
Register all tmux tools. Returns number of tools registered.
Imports
os re shlex shutil subprocess sys tool_registry
▶tool_registry.py 212 LOC
Tool plugin registry for dulus. Provides a central registry for tool definitions, lookup, schema export, and dispatch with output truncation.
Classes (1)
class ToolDef
Definition of a single tool plugin. Attributes: name: unique tool identifier schema: JSON-schema dict sent to the API (name, description, input_schema) func: callable(params: dict, config: dict) -> str read_only: True if the tool never mutates state concurrent_safe: True if s
Functions (8)
def register_tool(tool_def: ToolDef)
Register a tool, overwriting any existing tool with the same name.
def get_tool(name: str)
Look up a tool by name. Returns None if not found.
def get_all_tools()
Return all registered tools (insertion order).
def get_tool_schemas()
Return the schemas of all registered tools (for API tool parameter).
def is_display_only(name: str)
Check if a tool is display-only (visual output, don't read back). Returns True if the tool's output should not be fed back to the model, typically for ASCII art, visual charts, or display-only content.
def execute_tool(name: str, params: Dict[str, Any], config: Dict[str, Any], max_output: int = 10000)
Dispatch a tool call by name. Args: name: tool name params: tool input parameters dict config: runtime configuration dict max_output: maximum allowed output length in characters DEFAULT IS 2500— plugins/tools that need more MUST paginate explicitly. This prevents con
def clear_last_output()
Reset the last_tool_output.txt file. Should be called at turn start.
def clear_registry()
Remove all registered tools. Intended for testing.
Imports
__future__dataclasses json pathlib typing
▶tools.py 2558 LOC
Tool definitions and implementations for Dulus.
Functions (47)
def _is_in_tg_turn(config: dict)
Return True if the *current thread* is handling a Telegram interaction. Checks the thread-local flag first (set by the slash-command runner thread), then falls back to the config key (set by the main REPL for _bg_runner turns).
def _is_safe_bash(cmd: str)
def generate_unified_diff(old, new, filename, context_lines = 3)
def maybe_truncate_diff(diff_text, max_lines = 80)
def _read(file_path: str, limit: int = None, offset: int = None)
def _line_count(file_path: str)
def _print_last_output()
Print the full content of the last tool output directly. Use this to display large outputs (ASCII art, logs, etc.) without re-writing them.
def _search_last_output(pattern: str = None, context: int = 2)
Search or summarize the tool outputs accumulated during this turn.
def _write(file_path: str, content: str)
def _edit(file_path: str, old_string: str, new_string: str, replace_all: bool = False)
def _kill_proc_tree(pid: int)
Kill a process and all its children.
def _find_windows_bash()
Return (kind, path) for the best bash available on Windows, or None.
def _find_shell_by_type(shell_type: str, forced_path: str = '')
Find a specific shell type on Windows. Returns (kind, path) or None.
def _win_to_posix(path_str: str, wsl: bool = False)
Convert a Windows path string to POSIX for bash/WSL. C:\Users\foo → /c/Users/foo (gitbash) C:\Users\foo → /mnt/c/Users/foo (wsl)
def _is_bash_safe(command: str)
Check if a bash command passes the safety filter. Returns (is_safe, reason_if_unsafe).
def _bash(command: str, timeout: int = 30)
def _glob(pattern: str, path: str = None)
def _has_rg()
def _grep_python_pure(pattern: str, search_path: Path, glob_pat: str = None, output_mode: str = 'files_with_matches', case_insensitive: bool = False, context: int = 0)
Pure-Python grep fallback for Windows or when grep/rg misbehave.
def _grep(pattern: str, path: str = None, glob: str = None, output_mode: str = 'files_with_matches', case_insensitive: bool = False, context: int = 0)
def _libretranslate_host()
Return the best LibreTranslate host URL. In WSL2, localhost points to the WSL VM — use the Windows host IP instead (read from /etc/resolv.conf nameserver line). Falls back to localhost if not in WSL or can't parse.
def _clean_html(html: str)
Extract content text from HTML — only meaningful tags, strips noise.
def _libretranslate(text: str, source: str, target: str, host: str = None)
Translate via LibreTranslate (local). Returns None if unavailable. Splits into 800-char chunks to stay within API limits.
def _libretranslate_available()
def _webfetch(url: str)
Fetch URL → plain text.
def _bravesearch(query: str, api_key: str, country: str = None)
Search using Brave Search API.
def _websearch(query: str, config: dict = None, region: str = None)
def _parse_cell_id(cell_id: str)
Convert 'cell-N' shorthand to integer index; return None if not that form.
def _notebook_edit(notebook_path: str, new_source: str, cell_id: str = None, cell_type: str = None, edit_mode: str = 'replace')
def _detect_language(file_path: str)
def _run_quietly(cmd: list[str], cwd: str | None = None, timeout: int = 30)
Run a command, return (returncode, combined_output).
def _get_diagnostics(file_path: str, language: str = None)
def _ask_user_question(question: str, options: list[dict] | None = None, allow_freetext: bool = True, config: dict = None)
Block the agent loop and surface a question to the user in the terminal.
def ask_input_interactive(prompt: str, config: dict, menu_text: str = None)
Prompt the user for input, routing to Telegram if in a Telegram turn. If menu_text is provided, it is sent ahead of the prompt.
def drain_pending_questions(config: dict)
Called by the REPL loop after each streaming turn. Renders pending questions and collects user input. Returns True if any questions were answered.
def _sleeptimer(seconds: int, config: dict)
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)
Print content to the user's console. This tool displays text to the user WITHOUT consuming output tokens. The content is shown immediately in the chat console. If the conversation started via Telegram, also sends to Telegram. Args: content: Text to display (or use file_path to read from file)
def execute_tool(name: str, inputs: dict, permission_mode: str = 'auto', ask_permission: Optional[Callable[[str], bool]] = None, config: dict = None)
Dispatch tool execution; ask permission for write/destructive ops. Permission checking is done here, then delegation goes to the registry. The config dict is forwarded to tool functions so they can access runtime context like _depth, _system_prompt, model, etc.
def _register_builtins()
Register all built-in tools into the central registry.
def _enter_plan_mode(params: dict, config: dict)
Enter plan mode: read-only except plan file.
def _exit_plan_mode(params: dict, config: dict)
Exit plan mode and present plan for user approval.
def _plugin_list(params: dict, config: dict)
Implement the PluginList tool to query installed tools dynamically.
def _plugin_tools_list(params: dict, config: dict)
List all tools exposed by installed plugins.
def _read_job(params: dict, config: dict)
Read a job result by its ID. Simple way to get TmuxOffload results.
def _git_diff(params: dict, _config: dict)
def _git_status(_params: dict, _config: dict)
def _git_log(params: dict, _config: dict)
Imports
checkpoint.hooks difflib glob json mcp.tools memory.offload memory.tools multi_agent.tools os pathlib re skill.tools subprocess task.tools threading tool_registry typing
▶ui/__init__.py 1 LOC
▶ui/input.py 464 LOC
prompt_toolkit-based REPL input with typing-time slash-command autosuggest. Optional dependency: when prompt_toolkit is not installed, HAS_PROMPT_TOOLKIT is False and callers should fall through to readline-based input. Dependency-injected: callers register command/meta providers via setup() before calling read_line(). This module never imports Dulus core — keeping the dependency one-way and eliminating any circular-import risk.
Classes (1)
class _OutputRedirector
Redirects stdout to the split layout output buffer.
↳ __init__(self, original)
↳ write(self, text: str)
↳ flush(self)
↳ isatty(self)
Functions (11)
def setup(commands_provider: Callable[[], dict], meta_provider: Callable[[], dict])
Register providers for the live command registry and metadata. `commands_provider` returns the dispatcher's COMMANDS dict. `meta_provider` returns the _CMD_META dict (descriptions + subcommands).
def reset_session()
Drop the cached session so the next read_line() rebuilds from scratch.
def _build_session(history_path: Optional[Path])
def read_line(prompt_ansi: str, history_path: Optional[Path] = None)
Read one line of input via prompt_toolkit; caches the session across calls. The history file passed here MUST NOT be the readline history file — the two line-editors use incompatible formats. See Dulus REPL for the dedicated PT_HISTORY_FILE.
def read_line_split(prompt: str = '> ', history_path: Optional[Path] = None)
Read input with split layout - fixed bottom bar, scrollable output above. Similar to Kimi Code and Claude Code interfaces.
def append_output(text: str)
Append text to the output buffer (for split layout mode). Use this to display messages without interrupting the input bar.
def clear_split_output()
Clear the split layout output buffer.
def set_notification_callback(callback: Callable[[str], None])
Register a callback to handle background notifications. The callback will be called with the notification text when it's safe to display (during the next input cycle or when input is not active).
def queue_notification(text: str)
Queue a notification to be displayed safely. This should be used by background threads (timers, jobs, etc.) to display messages without corrupting the prompt_toolkit input bar.
def drain_notifications()
Drain all pending notifications from the queue. Returns a list of notification texts. Should be called when it's safe to display output (e.g., before showing a new prompt).
def safe_print_notification(text: str)
Print a notification in a prompt_toolkit-safe way. If split layout is active, uses append_output. Otherwise prints directly (which may cause display issues in sticky mode).
Imports
__future__pathlib queue typing
▶ui/render.py 299 LOC
ui/render.py — All terminal rendering for Dulus. Provides: - ANSI color helpers (C, clr, info, ok, warn, err) - Rich Markdown streaming (stream_text, flush_response) - Spinner management - Tool call display (print_tool_start, print_tool_end) - Diff rendering (render_diff)
Functions (22)
def clr(text: str, *keys)
def info(msg: str)
def ok(msg: str)
def warn(msg: str)
def err(msg: str)
def _truncate_err_global(s: str, max_len: int = 200)
def render_diff(text: str)
Print diff text with ANSI colors: red for removals, green for additions.
def _has_diff(text: str)
Check if text contains a unified diff.
def set_rich_live(enabled: bool)
Called from repl.py to apply the rich_live config setting.
def _make_renderable(text: str)
Return a Rich renderable: Markdown if text contains markup, else plain.
def _start_live()
Start a Rich Live block for in-place Markdown streaming (no-op if not Rich).
def stream_text(chunk: str)
Buffer chunk; update Live in-place when Rich available, else print directly. Safety: if accumulated text exceeds _LIVE_LINE_LIMIT lines, auto-switch from Rich Live to plain streaming to prevent terminal re-render duplication on terminals that can't handle large Live areas (macOS Terminal, etc.).
def stream_thinking(chunk: str, verbose: bool)
def flush_response()
Commit buffered text to screen: stop Live (freezes rendered Markdown in place).
def _run_tool_spinner()
Background spinner on a single line using carriage return.
def _start_tool_spinner()
def _change_spinner_phrase()
Change the spinner phrase without stopping it.
def set_spinner_phrase(phrase: str)
Set a specific spinner phrase (used by SSJ debate mode).
def _stop_tool_spinner()
def _tool_desc(name: str, inputs: dict)
def print_tool_start(name: str, inputs: dict, verbose: bool)
Show tool invocation.
def print_tool_end(name: str, result: str, verbose: bool)
Imports
__future__json sys threading
▶voice/__init__.py 56 LOC
Voice package for dulus. Public API ---------- check_voice_deps() → (available: bool, reason: str | None) record_once(...) → raw PCM bytes (int16, 16 kHz, mono) transcribe(...) → text string voice_input(...) → transcribed text (record + transcribe in one call)
Functions (2)
def check_voice_deps()
Return (available, reason_if_not).
def voice_input(language: str = 'auto', max_seconds: int = 30, on_energy: 'callable | None' = None, device_index: 'int | None' = None)
Record until silence, then transcribe. Returns transcribed text.
Imports
keyterms recorder stt tts
▶voice/keyterms.py 179 LOC
Voice keyterms: domain-specific vocabulary hints for STT accuracy. Passed as Whisper's `initial_prompt` so that coding terminology (grep, MCP, TypeScript, JSON, …) is recognised correctly instead of being mistranscribed as phonetically similar common words. Inspired by Claude Code's voiceKeyterms.ts, but expanded for a multi-provider setting and adapted to pull context from the Python runtime environment.
Functions (5)
def split_identifier(name: str)
Split camelCase / PascalCase / kebab-case / snake_case into words. Fragments ≤ 2 chars or > 20 chars are discarded. Examples: "dulus" → ["nano", "claude", "code"] "MyWebhookHandler" → ["My", "Webhook", "Handler"]
def _git_branch()
def _project_root()
Find the git root or fall back to cwd.
def _recent_py_files(root: Path, limit: int = 20)
Return the most-recently modified Python/TS/JS files in the repo.
def get_voice_keyterms(recent_files: list[str] | None = None)
Build a list of keyterms for the STT engine. Combines: • Hardcoded global coding vocabulary • Project root directory name • Git branch words • Recent source file stem words Returns up to MAX_KEYTERMS unique terms.
Imports
__future__pathlib re subprocess
▶voice/recorder.py 263 LOC
Audio capture for voice input. Backend priority (tried in order): 1. sounddevice — cross-platform, pure-Python wrapper around PortAudio. Best option: works on macOS, Linux, Windows. pip install sounddevice 2. arecord — Linux ALSA utility. No pip install needed. 3. sox rec — SoX command-line recorder. Supports silence detection. sudo apt install sox / brew install sox All backends capture raw PCM: 16 kHz, 16-
Functions (7)
def _has_cmd(cmd: str)
def check_recording_availability()
Return (available, reason_if_not).
def list_input_devices()
Return a list of available input devices with index and name.
def _record_sounddevice(max_seconds: int = 30, on_energy: 'callable | None' = None, device_index: 'int | None' = None)
def _record_arecord(max_seconds: int = 30, on_energy: 'callable | None' = None)
Record via arecord. Silence detection done in Python on the piped PCM.
def _record_sox(max_seconds: int = 30, on_energy: 'callable | None' = None)
Record via SoX `rec` with built-in silence detection.
def record_until_silence(max_seconds: int = 30, on_energy: 'callable | None' = None, device_index: 'int | None' = None)
Record from microphone until silence or max_seconds. Returns raw PCM bytes: int16, 16 kHz, mono. Tries backends in order: sounddevice → arecord → sox rec. Raises RuntimeError if no backend is available.
Imports
__future__io pathlib shutil subprocess threading
▶voice/stt.py 408 LOC
Speech-to-text (STT) backends. Backend priority (tried in order): 1. NVIDIA Riva — cloud, whisper-large-v3 via gRPC, needs NVIDIA_API_KEY. pip install nvidia-riva-client 2. faster-whisper — local, offline, fast, best for coding vocab. pip install faster-whisper 3. openai-whisper — local, offline, original OpenAI Whisper library. pip install openai-whisper 4. OpenAI Whisper API — cloud, needs OPENAI_API_KEY.
Functions (15)
def _riva_available()
Riva backend is usable iff the client lib is installed AND we have a key.
def _transcribe_nvidia_riva(pcm_bytes: bytes, language: Optional[str], translate: bool = False)
Transcribe via NVIDIA NVCF Riva (whisper-large-v3, gRPC). Riva expects a real audio container — we wrap raw PCM in WAV. `language=None` or "auto" → "multi" (Riva auto-detect). `translate=True` adds custom_configuration "task:translate" so foreign speech comes back as English.
def _audio_file_to_pcm(audio_bytes: bytes, suffix: str = '.ogg')
Convert an audio file (OGG, MP3, etc.) to raw int16 PCM (16kHz mono) via ffmpeg.
def _pcm_to_wav(pcm_bytes: bytes)
Wrap raw int16 PCM in a minimal WAV container.
def check_stt_availability()
Return (available, reason_if_not).
def get_stt_backend_name()
Return a human-readable name of the backend that will be used.
def _get_faster_whisper_model()
def _has_cuda()
def _transcribe_faster_whisper(pcm_bytes: bytes, keyterms: List[str], language: Optional[str])
def _get_openai_whisper_model()
def _transcribe_openai_whisper(pcm_bytes: bytes, keyterms: List[str], language: Optional[str])
def _transcribe_openai_api(pcm_bytes: bytes, language: Optional[str])
def _keyterms_to_prompt(keyterms: List[str])
Convert a list of keywords into a Whisper initial_prompt string. Whisper treats the initial_prompt as preceding context; sprinkling the coding vocabulary terms nudges the model to prefer these spellings.
def transcribe(pcm_bytes: bytes, keyterms: Optional[List[str]] = None, language: str = 'auto')
Transcribe raw PCM audio to text. Args: pcm_bytes: Raw int16 PCM, 16 kHz, mono. keyterms: Coding-domain vocabulary hints (improves accuracy). language: BCP-47 language code, or 'auto' for detection. Returns: Transcribed text, or empty string if audio contains no speech.
def transcribe_audio_file(audio_bytes: bytes, suffix: str = '.ogg', language: str = 'auto')
Transcribe an audio file (OGG, MP3, etc.) to text. Converts to PCM via ffmpeg, then runs through the STT pipeline. Falls back to OpenAI Whisper API (which accepts OGG natively) if ffmpeg is not available.
Imports
__future__io os pathlib recorder struct tempfile typing
▶voice/tts.py 449 LOC
Text-to-speech (TTS) backends. Backend priority (tried in order): 1. NVIDIA Riva — cloud, Magpie-Multilingual via NVCF gRPC. pip install nvidia-riva-client + NVIDIA_API_KEY 2. OpenAI TTS — cloud, high quality, needs OPENAI_API_KEY. 3. gTTS — cloud, free, needs internet. pip install gTTS 4. pyttsx3 — local, offline, uses system voices. pip install pyttsx3
Functions (16)
def _watch_for_cancel()
Background thread: set _stop_event if user presses 'c'.
def _play_audio_file(file_path: str | Path)
Play an audio file, interruptible with 'c' key.
def _play_windows_mci(file_path: str)
Play via MCI, polling _stop_event every 50ms to allow 'c' cancel.
def _get_pyttsx3_engine()
def _riva_lang_code(lang: str)
def _riva_voice_for(lang: str)
Resolve voice via env var (per-language first, then global, then default). Set DULUS_RIVA_TTS_VOICE_ES="Magpie-Multilingual.ES-US.Lupe" etc. to map voices per language. Run `talk.py --list-voices` once to discover names.
def _pcm_to_wav(pcm: bytes, sample_rate: int = 44100)
Wrap raw int16 mono PCM in a minimal WAV container.
def _riva_tts_available()
def _split_for_riva(text: str, limit: int = _RIVA_TTS_MAX_CHARS)
Split text into <=limit-char chunks at sentence/clause/word boundaries.
def _say_nvidia_riva(text: str, lang: str = 'es')
def _say_openai(text: str, voice: str = 'alloy', speed: float = 1.0)
def _say_gtts(text: str, lang: str = 'en')
def _say_pyttsx3(text: str, rate: int = 175)
def _clean_for_tts(text: str)
Strip markdown, HTML, emojis, and code blocks before speaking.
def say(text: str, voice: Optional[str] = None, speed: float = 1.0, lang: str = 'es')
Speak text using the best available TTS backend. Press 'c' to stop.
def check_tts_availability()
Return (available, reason_if_not).
Imports
__future__os pathlib re struct subprocess tempfile threading time typing
▶webchat.py 420 LOC
Dulus WebChat — standalone or in-process mirror of the terminal agent. When launched via /webchat from dulus.py, the in-process server in webchat_server.py is used instead. This file remains usable as a standalone fallback.
Functions (3)
def _run_agent_standalone(user_message: str)
Run agent loop with local state/config, yielding all events.
def create_app()
def main()
Imports
__future__agent argparse common config context flask json mcp.tools memory.tools multi_agent.tools queue skill.tools task.tools threading time tools typing uuid webbrowser
▶webchat_server.py 932 LOC
Dulus WebChat — in-process mirror of the terminal agent + Roundtable mode.
Classes (1)
class RoundtableAgent
↳ __init__(self, agent_id: str, model: str)
Functions (10)
def _ensure_plugin_tools()
def _run_agent_mirror(user_message: str)
Run the agent loop with shared state/config, yielding all events.
def _event_to_dict(event)
def _sanitize_for_api(text: str)
Aggressive sanitize: remove control chars (except ), surrogates, and normalize.
def _build_roundtable_prompt(agent: RoundtableAgent, user_msg: str, history: list[tuple[str, str]])
def _run_agent_for_roundtable(agent: RoundtableAgent, user_msg: str, history: list[tuple[str, str]], q: queue.Queue)
def create_app()
def start(state: AgentState, config: dict, port: int = 5000, open_browser: bool = False)
def stop()
def is_running()
Imports
__future__agent common context flask json mcp.tools memory.tools multi_agent.tools queue skill.tools task.tools threading time tools typing uuid webbrowser