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.
- robodog_terminal-0.2.0/PKG-INFO +51 -0
- robodog_terminal-0.2.0/TERMINAL_README.md +34 -0
- robodog_terminal-0.2.0/pyproject.toml +35 -0
- robodog_terminal-0.2.0/robodog_terminal/__init__.py +17 -0
- robodog_terminal-0.2.0/robodog_terminal/__main__.py +14 -0
- robodog_terminal-0.2.0/robodog_terminal/agents.py +159 -0
- robodog_terminal-0.2.0/robodog_terminal/app.py +1041 -0
- robodog_terminal-0.2.0/robodog_terminal/background.py +225 -0
- robodog_terminal-0.2.0/robodog_terminal/checkpoint.py +121 -0
- robodog_terminal-0.2.0/robodog_terminal/doctor.py +269 -0
- robodog_terminal-0.2.0/robodog_terminal/llm_client.py +328 -0
- robodog_terminal-0.2.0/robodog_terminal/loop.py +225 -0
- robodog_terminal-0.2.0/robodog_terminal/run_tests.py +85 -0
- robodog_terminal-0.2.0/robodog_terminal/selftest.py +138 -0
- robodog_terminal-0.2.0/robodog_terminal/sessions.py +202 -0
- robodog_terminal-0.2.0/robodog_terminal/skills.py +346 -0
- robodog_terminal-0.2.0/robodog_terminal/tasklist.py +215 -0
- robodog_terminal-0.2.0/robodog_terminal/test_app.py +228 -0
- robodog_terminal-0.2.0/robodog_terminal/test_background.py +141 -0
- robodog_terminal-0.2.0/robodog_terminal/test_command_reaction.py +106 -0
- robodog_terminal-0.2.0/robodog_terminal/test_concurrency.py +128 -0
- robodog_terminal-0.2.0/robodog_terminal/test_doctor.py +327 -0
- robodog_terminal-0.2.0/robodog_terminal/test_edit_quality.py +145 -0
- robodog_terminal-0.2.0/robodog_terminal/test_input.py +78 -0
- robodog_terminal-0.2.0/robodog_terminal/test_integration.py +127 -0
- robodog_terminal-0.2.0/robodog_terminal/test_llm_client.py +230 -0
- robodog_terminal-0.2.0/robodog_terminal/test_loop_checkpoint.py +192 -0
- robodog_terminal-0.2.0/robodog_terminal/test_rendering.py +185 -0
- robodog_terminal-0.2.0/robodog_terminal/test_sessions.py +205 -0
- robodog_terminal-0.2.0/robodog_terminal/test_skills.py +316 -0
- robodog_terminal-0.2.0/robodog_terminal/test_tasklist.py +211 -0
- robodog_terminal-0.2.0/robodog_terminal/test_toolcall.py +105 -0
- robodog_terminal-0.2.0/robodog_terminal/test_tools_scripts.py +129 -0
- robodog_terminal-0.2.0/robodog_terminal/test_turnrunner.py +153 -0
- robodog_terminal-0.2.0/robodog_terminal/toolcall.py +123 -0
- robodog_terminal-0.2.0/robodog_terminal/tools.py +810 -0
- robodog_terminal-0.2.0/robodog_terminal/turnrunner.py +175 -0
- robodog_terminal-0.2.0/robodog_terminal/ui.py +455 -0
- robodog_terminal-0.2.0/robodog_terminal.egg-info/PKG-INFO +51 -0
- robodog_terminal-0.2.0/robodog_terminal.egg-info/SOURCES.txt +43 -0
- robodog_terminal-0.2.0/robodog_terminal.egg-info/dependency_links.txt +1 -0
- robodog_terminal-0.2.0/robodog_terminal.egg-info/entry_points.txt +3 -0
- robodog_terminal-0.2.0/robodog_terminal.egg-info/requires.txt +6 -0
- robodog_terminal-0.2.0/robodog_terminal.egg-info/top_level.txt +1 -0
- 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
|
+
))
|