robodog-terminal 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. robodog_terminal-0.2.0/PKG-INFO +51 -0
  2. robodog_terminal-0.2.0/TERMINAL_README.md +34 -0
  3. robodog_terminal-0.2.0/pyproject.toml +35 -0
  4. robodog_terminal-0.2.0/robodog_terminal/__init__.py +17 -0
  5. robodog_terminal-0.2.0/robodog_terminal/__main__.py +14 -0
  6. robodog_terminal-0.2.0/robodog_terminal/agents.py +159 -0
  7. robodog_terminal-0.2.0/robodog_terminal/app.py +1041 -0
  8. robodog_terminal-0.2.0/robodog_terminal/background.py +225 -0
  9. robodog_terminal-0.2.0/robodog_terminal/checkpoint.py +121 -0
  10. robodog_terminal-0.2.0/robodog_terminal/doctor.py +269 -0
  11. robodog_terminal-0.2.0/robodog_terminal/llm_client.py +328 -0
  12. robodog_terminal-0.2.0/robodog_terminal/loop.py +225 -0
  13. robodog_terminal-0.2.0/robodog_terminal/run_tests.py +85 -0
  14. robodog_terminal-0.2.0/robodog_terminal/selftest.py +138 -0
  15. robodog_terminal-0.2.0/robodog_terminal/sessions.py +202 -0
  16. robodog_terminal-0.2.0/robodog_terminal/skills.py +346 -0
  17. robodog_terminal-0.2.0/robodog_terminal/tasklist.py +215 -0
  18. robodog_terminal-0.2.0/robodog_terminal/test_app.py +228 -0
  19. robodog_terminal-0.2.0/robodog_terminal/test_background.py +141 -0
  20. robodog_terminal-0.2.0/robodog_terminal/test_command_reaction.py +106 -0
  21. robodog_terminal-0.2.0/robodog_terminal/test_concurrency.py +128 -0
  22. robodog_terminal-0.2.0/robodog_terminal/test_doctor.py +327 -0
  23. robodog_terminal-0.2.0/robodog_terminal/test_edit_quality.py +145 -0
  24. robodog_terminal-0.2.0/robodog_terminal/test_input.py +78 -0
  25. robodog_terminal-0.2.0/robodog_terminal/test_integration.py +127 -0
  26. robodog_terminal-0.2.0/robodog_terminal/test_llm_client.py +230 -0
  27. robodog_terminal-0.2.0/robodog_terminal/test_loop_checkpoint.py +192 -0
  28. robodog_terminal-0.2.0/robodog_terminal/test_rendering.py +185 -0
  29. robodog_terminal-0.2.0/robodog_terminal/test_sessions.py +205 -0
  30. robodog_terminal-0.2.0/robodog_terminal/test_skills.py +316 -0
  31. robodog_terminal-0.2.0/robodog_terminal/test_tasklist.py +211 -0
  32. robodog_terminal-0.2.0/robodog_terminal/test_toolcall.py +105 -0
  33. robodog_terminal-0.2.0/robodog_terminal/test_tools_scripts.py +129 -0
  34. robodog_terminal-0.2.0/robodog_terminal/test_turnrunner.py +153 -0
  35. robodog_terminal-0.2.0/robodog_terminal/toolcall.py +123 -0
  36. robodog_terminal-0.2.0/robodog_terminal/tools.py +810 -0
  37. robodog_terminal-0.2.0/robodog_terminal/turnrunner.py +175 -0
  38. robodog_terminal-0.2.0/robodog_terminal/ui.py +455 -0
  39. robodog_terminal-0.2.0/robodog_terminal.egg-info/PKG-INFO +51 -0
  40. robodog_terminal-0.2.0/robodog_terminal.egg-info/SOURCES.txt +43 -0
  41. robodog_terminal-0.2.0/robodog_terminal.egg-info/dependency_links.txt +1 -0
  42. robodog_terminal-0.2.0/robodog_terminal.egg-info/entry_points.txt +3 -0
  43. robodog_terminal-0.2.0/robodog_terminal.egg-info/requires.txt +6 -0
  44. robodog_terminal-0.2.0/robodog_terminal.egg-info/top_level.txt +1 -0
  45. robodog_terminal-0.2.0/setup.cfg +4 -0
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: robodog-terminal
3
+ Version: 0.2.0
4
+ Summary: Agentic coding terminal for the shell (runPixel gateway / OpenAI-compatible / offline backends)
5
+ Author: adourish
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/adourish/robodog
8
+ Project-URL: Repository, https://github.com/adourish/robodog.git
9
+ Keywords: agent,coding,terminal,cli,gateway,llm,openrouter
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: rich>=13
13
+ Requires-Dist: prompt_toolkit>=3
14
+ Requires-Dist: requests>=2.25
15
+ Provides-Extra: yaml
16
+ Requires-Dist: PyYAML>=6; extra == "yaml"
17
+
18
+ # robodog-terminal
19
+
20
+ A agentic **agentic coding terminal**: a prompted tool-use loop that
21
+ reads/edits files, runs commands, runs tests, and self-corrects — over pluggable
22
+ LLM backends. Designed to run **leading models on self-hosted gateways**
23
+ (air-gapped, where an agentic coding terminal can't reach), and works equally with
24
+ OpenAI-compatible models or a fully offline mock.
25
+
26
+ ## Install
27
+ ```bash
28
+ pip install robodog-terminal # or: pip install -e robodogcli/robodog
29
+ ```
30
+
31
+ ## Run
32
+ ```bash
33
+ robodog-terminal --backend openai --model gpt-4o # live (OpenAI/OpenRouter)
34
+ robodog-terminal --backend gateway # enterprise box (the gateway / leading models)
35
+ robodog-terminal --echo # offline demo, no keys
36
+ robodog-terminal --backend openai -p "fix x.py and run the tests" # headless (-p)
37
+ python -m terminal.run_tests # 18 test suites
38
+ ```
39
+
40
+ ## Features
41
+ Agentic loop with an intent nudge + circuit breaker · tools (read/write/edit/
42
+ multi_edit/bash/run_script/run_tests/glob/grep/list_dir) with read-before-edit,
43
+ post-edit syntax verification and fuzzy edits · foreground + background subagents
44
+ (`/bg /tasks /tail /kill`) · per-prompt checkpoints with `/rewind` · JSONL
45
+ sessions (`/resume`, `--continue`) · plan mode · skills & custom commands
46
+ (`.robodog/`) · `CLAUDE.md`/`ROBODOG.md` hierarchy · a rich + prompt_toolkit TUI
47
+ with an emoji/color status line, clickable file & `file:line` links, multiline
48
+ paste, and mid-turn Ctrl+B backgrounding · headless `-p` (text/json) · `/doctor`.
49
+
50
+ Benchmarked at **capability parity with a leading agentic coding assistant** across 20 agentic
51
+ scenarios. See `robodogcli/docs/TERMINAL_MODE_PLAN.md` for the full design.
@@ -0,0 +1,34 @@
1
+ # robodog-terminal
2
+
3
+ A agentic **agentic coding terminal**: a prompted tool-use loop that
4
+ reads/edits files, runs commands, runs tests, and self-corrects — over pluggable
5
+ LLM backends. Designed to run **leading models on self-hosted gateways**
6
+ (air-gapped, where an agentic coding terminal can't reach), and works equally with
7
+ OpenAI-compatible models or a fully offline mock.
8
+
9
+ ## Install
10
+ ```bash
11
+ pip install robodog-terminal # or: pip install -e robodogcli/robodog
12
+ ```
13
+
14
+ ## Run
15
+ ```bash
16
+ robodog-terminal --backend openai --model gpt-4o # live (OpenAI/OpenRouter)
17
+ robodog-terminal --backend gateway # enterprise box (the gateway / leading models)
18
+ robodog-terminal --echo # offline demo, no keys
19
+ robodog-terminal --backend openai -p "fix x.py and run the tests" # headless (-p)
20
+ python -m terminal.run_tests # 18 test suites
21
+ ```
22
+
23
+ ## Features
24
+ Agentic loop with an intent nudge + circuit breaker · tools (read/write/edit/
25
+ multi_edit/bash/run_script/run_tests/glob/grep/list_dir) with read-before-edit,
26
+ post-edit syntax verification and fuzzy edits · foreground + background subagents
27
+ (`/bg /tasks /tail /kill`) · per-prompt checkpoints with `/rewind` · JSONL
28
+ sessions (`/resume`, `--continue`) · plan mode · skills & custom commands
29
+ (`.robodog/`) · `CLAUDE.md`/`ROBODOG.md` hierarchy · a rich + prompt_toolkit TUI
30
+ with an emoji/color status line, clickable file & `file:line` links, multiline
31
+ paste, and mid-turn Ctrl+B backgrounding · headless `-p` (text/json) · `/doctor`.
32
+
33
+ Benchmarked at **capability parity with a leading agentic coding assistant** across 20 agentic
34
+ scenarios. See `robodogcli/docs/TERMINAL_MODE_PLAN.md` for the full design.
@@ -0,0 +1,35 @@
1
+ # Standalone distribution for Terminal Mode.
2
+ # Build from this directory (robodogcli/robodog): python -m build
3
+ # Installs the `terminal` package plus the `robodog-terminal` / `robodogt` commands.
4
+ [build-system]
5
+ requires = ["setuptools>=61", "wheel"]
6
+ build-backend = "setuptools.build_meta"
7
+
8
+ [project]
9
+ name = "robodog-terminal"
10
+ version = "0.2.0"
11
+ description = "Agentic coding terminal for the shell (runPixel gateway / OpenAI-compatible / offline backends)"
12
+ readme = "TERMINAL_README.md"
13
+ requires-python = ">=3.8"
14
+ license = { text = "MIT" }
15
+ authors = [{ name = "adourish" }]
16
+ keywords = ["agent", "coding", "terminal", "cli", "gateway", "llm", "openrouter"]
17
+ dependencies = [
18
+ "rich>=13",
19
+ "prompt_toolkit>=3",
20
+ "requests>=2.25",
21
+ ]
22
+
23
+ [project.optional-dependencies]
24
+ yaml = ["PyYAML>=6"] # nicer .yaml verification in edits
25
+
26
+ [project.scripts]
27
+ robodog-terminal = "robodog_terminal.app:main"
28
+ robodogt = "robodog_terminal.app:main"
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/adourish/robodog"
32
+ Repository = "https://github.com/adourish/robodog.git"
33
+
34
+ [tool.setuptools]
35
+ packages = ["robodog_terminal"]
@@ -0,0 +1,17 @@
1
+ # file: robodog_terminal/__init__.py
2
+ """
3
+ Robodog Terminal Mode — an agentic interactive terminal.
4
+
5
+ Provides an agentic tool-use loop (prompted tool-calling), file editing,
6
+ and shell command execution, on top of pluggable LLM backends (the gateway / echo /
7
+ OpenAI-compatible). See docs/TERMINAL_MODE_PLAN.md.
8
+ """
9
+ __version__ = "0.2.0"
10
+ from .llm_client import LLMClient, Completion, EchoClient, GatewayClient
11
+ from .tools import ToolRegistry, default_registry
12
+ from .loop import AgentLoop
13
+
14
+ __all__ = [
15
+ "LLMClient", "Completion", "EchoClient", "GatewayClient",
16
+ "ToolRegistry", "default_registry", "AgentLoop",
17
+ ]
@@ -0,0 +1,14 @@
1
+ # file: robodog_terminal/__main__.py
2
+ """Entry point for `python -m robodog.robodog_terminal`."""
3
+ from __future__ import annotations
4
+
5
+ try:
6
+ from .app import main
7
+ except ImportError: # running the folder directly
8
+ import sys
9
+ from pathlib import Path
10
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
11
+ from robodog_terminal.app import main
12
+
13
+ if __name__ == "__main__":
14
+ raise SystemExit(main())
@@ -0,0 +1,159 @@
1
+ # file: robodog_terminal/agents.py
2
+ """
3
+ Subagents: an `agent` tool the model can call to delegate scoped work to a
4
+ child agent with its OWN context (a modern agentic terminal's Agent/Task tool).
5
+
6
+ Why this matters on the gateway especially: the parent re-sends its whole transcript
7
+ every iteration, so delegating a search/read-heavy job to a child — whose
8
+ transcript is DISCARDED, with only its final text returned as the tool result —
9
+ keeps the parent's context (and token bill) small.
10
+
11
+ Phase A (this file): foreground subagents — the child loop runs synchronously
12
+ inside the parent's tool call.
13
+ Phase B (later, needs background.py): background=true + task_output polling.
14
+
15
+ Depth cap = 1: child registries are built WITHOUT the `agent` tool, so
16
+ subagents cannot spawn subagents.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ from typing import Callable, Dict, Optional
21
+
22
+ from .loop import AgentLoop
23
+ from .tools import Tool, ToolParam, ToolRegistry, default_registry
24
+
25
+ AGENT_TYPES: Dict[str, dict] = {
26
+ "explore": {
27
+ "tools": ["read_file", "glob", "grep", "list_dir"], # read-only
28
+ "max_iterations": 10,
29
+ "note": (
30
+ "You are a READ-ONLY exploration subagent. Investigate the codebase to "
31
+ "answer the delegated question. You cannot modify files or run commands. "
32
+ "Your FINAL message (no tool blocks) must contain your complete findings — "
33
+ "it is the only thing returned to the caller."
34
+ ),
35
+ },
36
+ "general": {
37
+ "tools": None, # all tools (minus `agent` itself)
38
+ "max_iterations": 25,
39
+ "note": (
40
+ "You are a subagent completing a delegated task. Work autonomously until "
41
+ "done. Your FINAL message (no tool blocks) must summarize what you did and "
42
+ "the outcome — it is the only thing returned to the caller."
43
+ ),
44
+ },
45
+ }
46
+
47
+
48
+ def _child_registry(parent: ToolRegistry, agent_type: str) -> ToolRegistry:
49
+ """Fresh registry for the child: same cwd, filtered tools, never `agent`."""
50
+ child = default_registry(cwd=str(parent.cwd))
51
+ allowed = AGENT_TYPES[agent_type]["tools"]
52
+ child._tools = {
53
+ name: tool
54
+ for name, tool in child._tools.items()
55
+ if name != "agent" and (allowed is None or name in allowed)
56
+ }
57
+ return child
58
+
59
+
60
+ def register_agent_tool(
61
+ registry: ToolRegistry,
62
+ client,
63
+ on_child_event: Optional[Callable[[str, dict], None]] = None,
64
+ manager=None,
65
+ ) -> None:
66
+ """
67
+ Add the `agent` tool to `registry`. `client` is the shared LLMClient
68
+ (the client layer's semaphore caps the gateway concurrency). `on_child_event`
69
+ receives the child loop's events for indented rendering. `manager` is an
70
+ optional BackgroundManager enabling background=true subagents + the
71
+ task_output tool.
72
+ """
73
+
74
+ def _make_child(agent_type: str, cancel_event=None, events=None):
75
+ cfg = AGENT_TYPES[agent_type]
76
+ return AgentLoop(
77
+ client,
78
+ _child_registry(registry, agent_type),
79
+ max_iterations=cfg["max_iterations"],
80
+ on_event=events or on_child_event or (lambda k, d: None),
81
+ system_suffix=cfg["note"],
82
+ cancel_event=cancel_event,
83
+ )
84
+
85
+ def _agent(args: Dict[str, str]) -> str:
86
+ prompt = args["prompt"]
87
+ agent_type = (args.get("type") or "general").strip().lower()
88
+ background = str(args.get("background", "")).lower() in ("1", "true", "yes")
89
+ if agent_type not in AGENT_TYPES:
90
+ return (f"ERROR: unknown agent type '{agent_type}'. "
91
+ f"Available: {', '.join(AGENT_TYPES)}")
92
+
93
+ if background:
94
+ if manager is None:
95
+ return ("ERROR: background subagents unavailable (no manager); "
96
+ "run in foreground by omitting the background param.")
97
+
98
+ def target(task):
99
+ child = _make_child(
100
+ agent_type, cancel_event=task.cancel_event,
101
+ events=lambda k, d: task.emit(
102
+ f"⚙ {d.get('name', '')} " if k == "tool_start" else "")
103
+ if k == "tool_start" else None)
104
+ res = child.run(prompt)
105
+ return res.final_text
106
+
107
+ bg = manager.spawn("agent", f"{agent_type}: {prompt[:50]}", target)
108
+ return (f"Started background subagent {bg.id} ({agent_type}). "
109
+ f"Continue other work; fetch its result later with "
110
+ f'<tool name="task_output"><param name="id">{bg.id}</param></tool>.')
111
+
112
+ child = _make_child(agent_type)
113
+ result = child.run(prompt)
114
+ return (
115
+ f"[subagent:{agent_type} finished — {result.iterations} steps, "
116
+ f"{result.total_tokens} tokens]\n{result.final_text}"
117
+ )
118
+
119
+ registry.register(Tool(
120
+ name="agent",
121
+ description=(
122
+ "Delegate a scoped task to a subagent with its own fresh context. "
123
+ "Use type=explore for read-only codebase investigation (searching, "
124
+ "reading, summarizing) and type=general for delegated work that may "
125
+ "edit files or run commands. The subagent's final message is returned "
126
+ "to you; its intermediate work is not. Subagents cannot spawn subagents. "
127
+ "PARALLEL FAN-OUT: to run subagents in parallel, emit SEVERAL agent "
128
+ "calls (foreground, background NOT set) in ONE response — they run "
129
+ "concurrently and all results return together in that same turn. "
130
+ "Do NOT use background=true for parallelism; background is only for "
131
+ "detaching a long task to poll later with task_output."
132
+ ),
133
+ params=[
134
+ ToolParam("prompt", "Complete, self-contained task description for the subagent."),
135
+ ToolParam("type", "explore | general (default general).", required=False),
136
+ ToolParam("background", "true to run concurrently (default false).", required=False),
137
+ ],
138
+ handler=_agent,
139
+ mutating=True, # general-type children can mutate
140
+ ))
141
+
142
+ if manager is not None:
143
+ def _task_output(args: Dict[str, str]) -> str:
144
+ task_id = args["id"].strip()
145
+ task = manager.get(task_id)
146
+ if task is None:
147
+ return f"ERROR: no such task '{task_id}'."
148
+ if task.status == "running":
149
+ tail = manager.output(task_id, tail=10)
150
+ return (f"{task_id} still running ({task.kind}). Recent output:\n{tail}"
151
+ if tail.strip() else f"{task_id} still running ({task.kind}).")
152
+ return f"{task_id} {task.status}.\n{task.result or manager.output(task_id)}"
153
+
154
+ registry.register(Tool(
155
+ name="task_output",
156
+ description="Get the status/result of a background task or subagent by id.",
157
+ params=[ToolParam("id", "Task id, e.g. bg1.")],
158
+ handler=_task_output,
159
+ ))