rlmness-cli 0.2.0__py3-none-any.whl
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.
- rlmagent/__init__.py +1 -0
- rlmagent/__main__.py +8 -0
- rlmagent/acp/__init__.py +1 -0
- rlmagent/acp/runtime.py +71 -0
- rlmagent/acp/server.py +598 -0
- rlmagent/acp/updates.py +128 -0
- rlmagent/agentnet.py +237 -0
- rlmagent/agents/__init__.py +1 -0
- rlmagent/agents/approval.py +90 -0
- rlmagent/agents/bridge.py +109 -0
- rlmagent/agents/budget.py +212 -0
- rlmagent/agents/child_handles.py +46 -0
- rlmagent/agents/child_mailbox.py +30 -0
- rlmagent/agents/kernel_api.py +156 -0
- rlmagent/agents/output.py +34 -0
- rlmagent/agents/patch.py +107 -0
- rlmagent/agents/prompts.py +156 -0
- rlmagent/agents/trace.py +290 -0
- rlmagent/agents/tree.py +682 -0
- rlmagent/agents/wasm_api.py +105 -0
- rlmagent/agents/workspace.py +357 -0
- rlmagent/autopilot.py +447 -0
- rlmagent/backend.py +946 -0
- rlmagent/backend_context.py +74 -0
- rlmagent/cli/__init__.py +5 -0
- rlmagent/cli/config_cmd.py +177 -0
- rlmagent/cli/health.py +156 -0
- rlmagent/cli/main.py +1194 -0
- rlmagent/cli/update.py +188 -0
- rlmagent/clipboard.py +63 -0
- rlmagent/config/__init__.py +37 -0
- rlmagent/config/config.py +297 -0
- rlmagent/config/loader.py +119 -0
- rlmagent/config/prompts.py +124 -0
- rlmagent/config/secure_input.py +213 -0
- rlmagent/config/store.py +275 -0
- rlmagent/context/__init__.py +47 -0
- rlmagent/context/budget.py +557 -0
- rlmagent/contract.py +311 -0
- rlmagent/contract_run.py +232 -0
- rlmagent/conversation.py +2547 -0
- rlmagent/cron.py +77 -0
- rlmagent/directives.py +893 -0
- rlmagent/discovery.py +356 -0
- rlmagent/execution_log.py +66 -0
- rlmagent/factory.py +189 -0
- rlmagent/factory_graph.py +413 -0
- rlmagent/harness/__init__.py +63 -0
- rlmagent/harness/contracts/__init__.py +1 -0
- rlmagent/harness/contracts/stream.py +129 -0
- rlmagent/harness/contracts/tooling.py +127 -0
- rlmagent/harness/contracts/transcript/__init__.py +68 -0
- rlmagent/harness/contracts/transcript/_base.py +29 -0
- rlmagent/harness/contracts/transcript/diagnostics.py +52 -0
- rlmagent/harness/contracts/transcript/entries.py +180 -0
- rlmagent/harness/contracts/transcript/segments.py +64 -0
- rlmagent/harness/contracts/values.py +6 -0
- rlmagent/harness/driver.py +397 -0
- rlmagent/harness/engine.py +489 -0
- rlmagent/harness/mending.py +153 -0
- rlmagent/harness/provider/__init__.py +1 -0
- rlmagent/harness/provider/base.py +27 -0
- rlmagent/harness/provider/wire.py +189 -0
- rlmagent/harness/py.typed +0 -0
- rlmagent/harness/session/__init__.py +5 -0
- rlmagent/harness/session/index.py +218 -0
- rlmagent/harness/session/lease.py +66 -0
- rlmagent/harness/session/records.py +118 -0
- rlmagent/harness/session/replay.py +165 -0
- rlmagent/harness/session/store.py +155 -0
- rlmagent/harness/session/summary.py +46 -0
- rlmagent/hints.py +47 -0
- rlmagent/history.py +92 -0
- rlmagent/hooks/__init__.py +1 -0
- rlmagent/hooks/hooks.py +1 -0
- rlmagent/instructions.py +157 -0
- rlmagent/job_runner.py +117 -0
- rlmagent/jobs.py +365 -0
- rlmagent/kept_agents.py +397 -0
- rlmagent/kernel/__init__.py +5 -0
- rlmagent/kernel/session.py +205 -0
- rlmagent/kernel/wasm.py +169 -0
- rlmagent/logs.py +208 -0
- rlmagent/managed_release.py +368 -0
- rlmagent/mcp.py +411 -0
- rlmagent/mcp_oauth.py +213 -0
- rlmagent/model/__init__.py +29 -0
- rlmagent/model/_claude/__init__.py +1 -0
- rlmagent/model/_claude/courier.py +117 -0
- rlmagent/model/_claude/emitter.py +378 -0
- rlmagent/model/_oai/__init__.py +1 -0
- rlmagent/model/_oai/helpers.py +256 -0
- rlmagent/model/_oai/normalize.py +263 -0
- rlmagent/model/_oai/parsers.py +317 -0
- rlmagent/model/_oai/payloads.py +226 -0
- rlmagent/model/_oai/transport.py +127 -0
- rlmagent/model/claude.py +552 -0
- rlmagent/model/copilot.py +142 -0
- rlmagent/model/correlation.py +21 -0
- rlmagent/model/deadline.py +19 -0
- rlmagent/model/discovery.py +181 -0
- rlmagent/model/external/__init__.py +11 -0
- rlmagent/model/external/acp.py +202 -0
- rlmagent/model/external/acp_install.py +109 -0
- rlmagent/model/external/cli.py +248 -0
- rlmagent/model/external/textcalls.py +57 -0
- rlmagent/model/failover.py +129 -0
- rlmagent/model/google.py +199 -0
- rlmagent/model/limits.py +46 -0
- rlmagent/model/oai_compatible.py +414 -0
- rlmagent/model/preferences.py +56 -0
- rlmagent/model/py.typed +0 -0
- rlmagent/model/registered_oauth.py +195 -0
- rlmagent/model/scripted.py +55 -0
- rlmagent/model/settings.py +271 -0
- rlmagent/model/subscription.py +398 -0
- rlmagent/model/token_bounds.py +15 -0
- rlmagent/model/transport/__init__.py +1 -0
- rlmagent/model/transport/backoff.py +87 -0
- rlmagent/model/transport/client.py +87 -0
- rlmagent/model/transport/faults.py +65 -0
- rlmagent/model/transport/responses_socket.py +223 -0
- rlmagent/model/vertex.py +93 -0
- rlmagent/network.py +123 -0
- rlmagent/packages.py +246 -0
- rlmagent/planning.py +84 -0
- rlmagent/playbook/__init__.py +1 -0
- rlmagent/playbook/apply.py +106 -0
- rlmagent/playbook/digest.py +63 -0
- rlmagent/playbook/improver.py +272 -0
- rlmagent/playbook/plan.py +99 -0
- rlmagent/playbook/store.py +67 -0
- rlmagent/plugins.py +327 -0
- rlmagent/policy.py +105 -0
- rlmagent/prompt_refs.py +57 -0
- rlmagent/prompts.py +331 -0
- rlmagent/prune.py +60 -0
- rlmagent/py.typed +0 -0
- rlmagent/reasoning.py +135 -0
- rlmagent/refinement/__init__.py +1 -0
- rlmagent/refinement/controller.py +448 -0
- rlmagent/refinement/engine.py +220 -0
- rlmagent/refinement/evidence.py +131 -0
- rlmagent/refinement/scoped.py +90 -0
- rlmagent/refinement/store.py +193 -0
- rlmagent/refresh.py +202 -0
- rlmagent/routing.py +52 -0
- rlmagent/runtime.py +575 -0
- rlmagent/safety.py +267 -0
- rlmagent/session_html.py +57 -0
- rlmagent/session_import.py +52 -0
- rlmagent/session_share.py +55 -0
- rlmagent/sessions.py +249 -0
- rlmagent/shell_sandbox.py +88 -0
- rlmagent/skillset.py +320 -0
- rlmagent/steering.py +148 -0
- rlmagent/stops.py +62 -0
- rlmagent/subscriptions.py +219 -0
- rlmagent/tasks.py +98 -0
- rlmagent/toolhost.py +310 -0
- rlmagent/tools/__init__.py +56 -0
- rlmagent/tools/_shared.py +210 -0
- rlmagent/tools/files.py +462 -0
- rlmagent/tools/kernel.py +93 -0
- rlmagent/tools/patch_tool.py +68 -0
- rlmagent/tools/search.py +188 -0
- rlmagent/trust.py +383 -0
- rlmagent/tui/__init__.py +1 -0
- rlmagent/tui/adapter.py +506 -0
- rlmagent/tui/app.py +711 -0
- rlmagent/tui/approval_screen.py +57 -0
- rlmagent/tui/choice_screen.py +47 -0
- rlmagent/tui/completions.py +116 -0
- rlmagent/tui/components.py +25 -0
- rlmagent/tui/composer.py +40 -0
- rlmagent/tui/factory_view.py +86 -0
- rlmagent/tui/inspect.py +193 -0
- rlmagent/tui/messages.py +215 -0
- rlmagent/tui/model_screen.py +307 -0
- rlmagent/tui/prompt.py +98 -0
- rlmagent/tui/question_screen.py +52 -0
- rlmagent/tui/replay.py +141 -0
- rlmagent/tui/rlm_agent.tcss +405 -0
- rlmagent/tui/search_box.py +91 -0
- rlmagent/tui/sidebar.py +182 -0
- rlmagent/tui/status.py +49 -0
- rlmagent/tui/steps.py +283 -0
- rlmagent/tui/suggest.py +778 -0
- rlmagent/tui/transcript.py +172 -0
- rlmagent/tui/trust_dialog.py +58 -0
- rlmagent/tui/worker_attach.py +141 -0
- rlmagent/ui/__init__.py +1 -0
- rlmagent/ui/render.py +211 -0
- rlmagent/undo.py +109 -0
- rlmagent/utils/__init__.py +1 -0
- rlmagent/websearch.py +75 -0
- rlmagent/worker.py +1275 -0
- rlmagent/worker_service.py +203 -0
- rlmness_cli-0.2.0.dist-info/METADATA +882 -0
- rlmness_cli-0.2.0.dist-info/RECORD +203 -0
- rlmness_cli-0.2.0.dist-info/WHEEL +4 -0
- rlmness_cli-0.2.0.dist-info/entry_points.txt +2 -0
- rlmness_cli-0.2.0.dist-info/licenses/LICENSE +21 -0
rlmagent/tui/replay.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Saved runs read back: step cards for a past session, and per-agent totals."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from textual.app import App, ComposeResult
|
|
9
|
+
from textual.binding import Binding
|
|
10
|
+
from textual.widgets import Footer, Static
|
|
11
|
+
|
|
12
|
+
from rlmagent.tui.steps import StepBook, short_id
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def events_path(sessions: Path, session_id: str) -> Path:
|
|
16
|
+
return sessions / f"{session_id}.backend.events.jsonl"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_events(path: Path) -> list[dict]:
|
|
20
|
+
"""The run events a session saved, skipping lines that do not parse."""
|
|
21
|
+
events = []
|
|
22
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
23
|
+
try:
|
|
24
|
+
event = json.loads(line)
|
|
25
|
+
except json.JSONDecodeError:
|
|
26
|
+
continue
|
|
27
|
+
if isinstance(event, dict):
|
|
28
|
+
events.append(event)
|
|
29
|
+
return events
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def runs_of(events: list[dict]) -> list[StepBook]:
|
|
33
|
+
"""One book per run: a root run_started begins the next one."""
|
|
34
|
+
books: list[StepBook] = []
|
|
35
|
+
for event in events:
|
|
36
|
+
if event.get("kind") == "run_started" and not event.get("parent_run_id"):
|
|
37
|
+
books.append(StepBook())
|
|
38
|
+
if not books:
|
|
39
|
+
books.append(StepBook())
|
|
40
|
+
books[-1].feed(event)
|
|
41
|
+
return books
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def agent_rows(books: list[StepBook]) -> list[dict]:
|
|
45
|
+
"""Per-agent steps, tokens, cost and seconds over every run of a session."""
|
|
46
|
+
rows = []
|
|
47
|
+
for run, book in enumerate(books, 1):
|
|
48
|
+
for agent in book.agents.values():
|
|
49
|
+
steps = [book.steps[(agent.run_id, n)] for n in agent.steps]
|
|
50
|
+
costs = [s.cost for s in steps if s.cost is not None]
|
|
51
|
+
seconds = [s.seconds for s in steps if s.seconds is not None]
|
|
52
|
+
rows.append({
|
|
53
|
+
"run": run,
|
|
54
|
+
"agent": agent.run_id,
|
|
55
|
+
"parent": agent.parent,
|
|
56
|
+
"depth": agent.depth,
|
|
57
|
+
"status": agent.status,
|
|
58
|
+
"steps": len(steps),
|
|
59
|
+
"failed_steps": sum(1 for s in steps if s.state == "failed"),
|
|
60
|
+
"tokens": sum(s.tokens for s in steps),
|
|
61
|
+
"cost": round(sum(costs), 6) if costs else None,
|
|
62
|
+
"seconds": round(sum(seconds), 2) if seconds else None,
|
|
63
|
+
})
|
|
64
|
+
return rows
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def format_rows(rows: list[dict]) -> str:
|
|
68
|
+
if not rows:
|
|
69
|
+
return "No agent steps were saved for this session."
|
|
70
|
+
lines = [f"{'run':>3} {'agent':<8} {'depth':>5} {'status':<9} {'steps':>5} "
|
|
71
|
+
f"{'tokens':>8} {'cost':>9} {'seconds':>8}"]
|
|
72
|
+
for row in rows:
|
|
73
|
+
cost = "?" if row["cost"] is None else f"${row['cost']:.4f}"
|
|
74
|
+
seconds = "?" if row["seconds"] is None else f"{row['seconds']:.1f}"
|
|
75
|
+
lines.append(f"{row['run']:>3} {short_id(row['agent']):<8} {row['depth']:>5} "
|
|
76
|
+
f"{row['status']:<9} {row['steps']:>5} {row['tokens']:>8} "
|
|
77
|
+
f"{cost:>9} {seconds:>8}")
|
|
78
|
+
tokens = sum(r["tokens"] for r in rows)
|
|
79
|
+
costs = [r["cost"] for r in rows if r["cost"] is not None]
|
|
80
|
+
total = f"${sum(costs):.4f}" if costs else "unknown"
|
|
81
|
+
lines.append(f"total: {len(rows)} agents, {sum(r['steps'] for r in rows)} steps, "
|
|
82
|
+
f"{tokens} tokens, cost {total}")
|
|
83
|
+
return "\n".join(lines)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ReplayApp(App[None]):
|
|
87
|
+
"""A saved session's steps as cards, read only."""
|
|
88
|
+
|
|
89
|
+
CSS_PATH = Path(__file__).with_name("rlm_agent.tcss")
|
|
90
|
+
TITLE = "rlm-agent replay"
|
|
91
|
+
BINDINGS = [Binding("q", "quit", "Quit"), Binding("f2", "agent_tree", "Agents"),
|
|
92
|
+
Binding("f3", "timeline", "Timeline")]
|
|
93
|
+
|
|
94
|
+
def __init__(self, session_id: str, books: list[StepBook]) -> None:
|
|
95
|
+
super().__init__()
|
|
96
|
+
self.session_id = session_id
|
|
97
|
+
self.books = books
|
|
98
|
+
|
|
99
|
+
def compose(self) -> ComposeResult:
|
|
100
|
+
from rlmagent.tui.transcript import TranscriptView
|
|
101
|
+
|
|
102
|
+
yield Static(f"session {self.session_id} · {len(self.books)} runs", classes="step-foot")
|
|
103
|
+
yield TranscriptView()
|
|
104
|
+
yield Footer()
|
|
105
|
+
|
|
106
|
+
def on_mount(self) -> None:
|
|
107
|
+
from rlmagent.tui.transcript import TranscriptView
|
|
108
|
+
|
|
109
|
+
view = self.query_one(TranscriptView)
|
|
110
|
+
for run, book in enumerate(self.books, 1):
|
|
111
|
+
view.add_notice(f"run {run}")
|
|
112
|
+
view.new_run()
|
|
113
|
+
for key in book.order:
|
|
114
|
+
view.show_step(book.steps[key])
|
|
115
|
+
|
|
116
|
+
def on_step_card_inspect(self, event) -> None:
|
|
117
|
+
from rlmagent.tui.inspect import StepScreen
|
|
118
|
+
|
|
119
|
+
self.push_screen(StepScreen(event.step))
|
|
120
|
+
|
|
121
|
+
def _merged(self) -> StepBook:
|
|
122
|
+
merged = StepBook()
|
|
123
|
+
for book in self.books:
|
|
124
|
+
merged.agents.update(book.agents)
|
|
125
|
+
merged.steps.update(book.steps)
|
|
126
|
+
merged.child_names.update(book.child_names)
|
|
127
|
+
merged.child_notes.update(book.child_notes)
|
|
128
|
+
return merged
|
|
129
|
+
|
|
130
|
+
def action_agent_tree(self) -> None:
|
|
131
|
+
from rlmagent.tui.inspect import AgentTreeScreen
|
|
132
|
+
|
|
133
|
+
self.push_screen(AgentTreeScreen(self._merged()))
|
|
134
|
+
|
|
135
|
+
def action_timeline(self) -> None:
|
|
136
|
+
from rlmagent.tui.inspect import TimelineScreen
|
|
137
|
+
|
|
138
|
+
self.push_screen(TimelineScreen(self.books[-1] if self.books else StepBook()))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
__all__ = ["ReplayApp", "agent_rows", "events_path", "format_rows", "load_events", "runs_of"]
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/* rlm-agent TUI — black, minimal, no chrome colour.
|
|
2
|
+
Geometry and palette live here so widget modules stay presentation-free. */
|
|
3
|
+
|
|
4
|
+
Screen {
|
|
5
|
+
layout: vertical;
|
|
6
|
+
background: #000000;
|
|
7
|
+
color: #c8c8c8;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
Header {
|
|
11
|
+
background: #000000;
|
|
12
|
+
color: #8a8a8a;
|
|
13
|
+
text-style: none;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
Footer {
|
|
17
|
+
background: #000000;
|
|
18
|
+
color: #6a6a6a;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
FooterKey {
|
|
22
|
+
background: #000000;
|
|
23
|
+
color: #6a6a6a;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
FooterKey > .footer-key--key {
|
|
27
|
+
background: #000000;
|
|
28
|
+
color: #9a9a9a;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Row between header and footer: sidebar | main column. */
|
|
32
|
+
#body {
|
|
33
|
+
height: 1fr;
|
|
34
|
+
width: 100%;
|
|
35
|
+
background: #000000;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Sidebar {
|
|
39
|
+
width: 28;
|
|
40
|
+
min-width: 18;
|
|
41
|
+
padding: 1 1;
|
|
42
|
+
background: #000000;
|
|
43
|
+
border-right: solid #1e1e1e;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#sidebar-title {
|
|
47
|
+
padding: 0 1 1 1;
|
|
48
|
+
color: #5a5a5a;
|
|
49
|
+
text-style: none;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#session-list {
|
|
53
|
+
height: 1fr;
|
|
54
|
+
background: #000000;
|
|
55
|
+
border: none;
|
|
56
|
+
scrollbar-size: 0 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#session-list > ListItem {
|
|
60
|
+
padding: 0 1;
|
|
61
|
+
background: #000000;
|
|
62
|
+
color: #7a7a7a;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#session-list > ListItem.active-session {
|
|
66
|
+
color: #e8e8e8;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#session-list > ListItem.-highlight {
|
|
70
|
+
background: #000000;
|
|
71
|
+
color: #c9a4ff;
|
|
72
|
+
text-style: none;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#main {
|
|
76
|
+
width: 1fr;
|
|
77
|
+
height: 100%;
|
|
78
|
+
background: #000000;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Transcript flexes; the composer below it is fixed height. */
|
|
82
|
+
TranscriptView {
|
|
83
|
+
height: 1fr;
|
|
84
|
+
padding: 1 2;
|
|
85
|
+
background: #000000;
|
|
86
|
+
scrollbar-size: 0 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* ── composer ─────────────────────────────────────────────────────── */
|
|
90
|
+
|
|
91
|
+
Composer {
|
|
92
|
+
height: 3;
|
|
93
|
+
background: #000000;
|
|
94
|
+
border: solid #1e1e1e;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
Composer:focus-within {
|
|
98
|
+
border: solid #333333;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#prompt-mark {
|
|
102
|
+
width: 3;
|
|
103
|
+
padding: 0 1;
|
|
104
|
+
color: #c9a4ff;
|
|
105
|
+
background: #000000;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
PromptInput {
|
|
109
|
+
width: 1fr;
|
|
110
|
+
height: 1fr;
|
|
111
|
+
background: #000000;
|
|
112
|
+
border: none;
|
|
113
|
+
padding: 0;
|
|
114
|
+
scrollbar-size: 0 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#model-badge {
|
|
118
|
+
width: auto;
|
|
119
|
+
padding: 0 1;
|
|
120
|
+
color: #5a5a5a;
|
|
121
|
+
background: #000000;
|
|
122
|
+
content-align: right middle;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* ── autocomplete ─────────────────────────────────────────────────── */
|
|
126
|
+
|
|
127
|
+
#completion-bar {
|
|
128
|
+
height: auto;
|
|
129
|
+
max-height: 8;
|
|
130
|
+
background: #0a0a0a;
|
|
131
|
+
border: solid #1e1e1e;
|
|
132
|
+
padding: 0 1;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#completion-body {
|
|
136
|
+
color: #7a7a7a;
|
|
137
|
+
background: #0a0a0a;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* ── model picker modal ───────────────────────────────────────────── */
|
|
141
|
+
|
|
142
|
+
ModelScreen {
|
|
143
|
+
align: center middle;
|
|
144
|
+
background: #000000 70%;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#model-dialog {
|
|
148
|
+
width: 52;
|
|
149
|
+
height: auto;
|
|
150
|
+
max-height: 16;
|
|
151
|
+
padding: 1 2;
|
|
152
|
+
background: #0a0a0a;
|
|
153
|
+
border: solid #333333;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#model-title {
|
|
157
|
+
padding: 0 0 1 0;
|
|
158
|
+
color: #e8e8e8;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#model-list {
|
|
162
|
+
height: auto;
|
|
163
|
+
max-height: 8;
|
|
164
|
+
background: #0a0a0a;
|
|
165
|
+
border: none;
|
|
166
|
+
scrollbar-size: 0 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#model-list > ListItem {
|
|
170
|
+
padding: 0 1;
|
|
171
|
+
background: #0a0a0a;
|
|
172
|
+
color: #9a9a9a;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#model-list > ListItem.-highlight {
|
|
176
|
+
background: #0a0a0a;
|
|
177
|
+
color: #c9a4ff;
|
|
178
|
+
text-style: none;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
#model-key {
|
|
182
|
+
background: #0a0a0a;
|
|
183
|
+
color: #e8e8e8;
|
|
184
|
+
border: solid #1e1e1e;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#model-key:focus {
|
|
188
|
+
border: solid #333333;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
#model-hint {
|
|
192
|
+
padding: 1 0 0 0;
|
|
193
|
+
color: #5a5a5a;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* ── status ───────────────────────────────────────────────────────── */
|
|
197
|
+
|
|
198
|
+
#status-bar {
|
|
199
|
+
height: 1;
|
|
200
|
+
padding: 0 1;
|
|
201
|
+
background: #000000;
|
|
202
|
+
color: #5a5a5a;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* Visible while a generation is running and can be stopped. */
|
|
206
|
+
#status-bar.busy {
|
|
207
|
+
color: #c9a4ff;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* ── transcript blocks ────────────────────────────────────────────── */
|
|
211
|
+
|
|
212
|
+
.user-block {
|
|
213
|
+
padding: 1 0 0 0;
|
|
214
|
+
color: #e8e8e8;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.assistant-block {
|
|
218
|
+
padding: 0 0 1 0;
|
|
219
|
+
color: #b4b4b4;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.tool-code {
|
|
223
|
+
padding: 0 0 0 4;
|
|
224
|
+
height: auto;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.tool-line {
|
|
228
|
+
padding: 0 0 0 2;
|
|
229
|
+
color: #6a6a6a;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.tool-error {
|
|
233
|
+
color: #a05050;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.tool-output {
|
|
237
|
+
padding: 0 0 0 2;
|
|
238
|
+
height: auto;
|
|
239
|
+
color: #6a6a6a;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.notice-block {
|
|
243
|
+
padding: 0 0 1 0;
|
|
244
|
+
color: #5a5a5a;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
#approval-dialog {
|
|
248
|
+
width: 80;
|
|
249
|
+
height: auto;
|
|
250
|
+
max-height: 30;
|
|
251
|
+
padding: 1 2;
|
|
252
|
+
background: #0a0a0a;
|
|
253
|
+
border: solid #333333;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#approval-body {
|
|
257
|
+
max-height: 18;
|
|
258
|
+
overflow-y: auto;
|
|
259
|
+
margin: 1 0;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* Step cards: one per agent step, indented by depth in code. */
|
|
263
|
+
.step-card {
|
|
264
|
+
height: auto;
|
|
265
|
+
margin-bottom: 1;
|
|
266
|
+
padding: 0 1;
|
|
267
|
+
border-left: solid #2a2a2a;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.step-card.step-live {
|
|
271
|
+
border-left: solid #c9a4ff;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.step-card.step-failed {
|
|
275
|
+
border-left: solid #d36a6a;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.step-head {
|
|
279
|
+
color: #8a8a8a;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.step-card.step-live > .step-head {
|
|
283
|
+
color: #c9a4ff;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.step-output {
|
|
287
|
+
color: #9a9a9a;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.step-foot {
|
|
291
|
+
color: #5a5a5a;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.step-card:focus {
|
|
295
|
+
background: #0d0d0d;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.inspect-screen {
|
|
299
|
+
align: center middle;
|
|
300
|
+
background: #000000 80%;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.inspect-body {
|
|
304
|
+
width: 90%;
|
|
305
|
+
height: 90%;
|
|
306
|
+
padding: 1 2;
|
|
307
|
+
background: #000000;
|
|
308
|
+
border: solid #2a2a2a;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#task-panel {
|
|
312
|
+
height: auto;
|
|
313
|
+
max-height: 50%;
|
|
314
|
+
padding: 1 1 0 1;
|
|
315
|
+
color: #8a8a8a;
|
|
316
|
+
border-top: solid #1e1e1e;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* ── folded errors, shell box, echoes, side answers, queue ───────── */
|
|
320
|
+
|
|
321
|
+
.error-block {
|
|
322
|
+
height: auto;
|
|
323
|
+
padding: 0 0 1 0;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.shell-block {
|
|
327
|
+
height: auto;
|
|
328
|
+
margin: 0 0 1 2;
|
|
329
|
+
padding: 0 1;
|
|
330
|
+
color: #9a9a9a;
|
|
331
|
+
border: round #333333;
|
|
332
|
+
border-title-color: #c9a4ff;
|
|
333
|
+
border-subtitle-color: #5a5a5a;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.shell-block.shell-failed {
|
|
337
|
+
border: round #a05050;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.command-echo {
|
|
341
|
+
padding: 1 0 0 0;
|
|
342
|
+
color: #9a9a9a;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.side-answer {
|
|
346
|
+
height: auto;
|
|
347
|
+
margin: 0 0 1 2;
|
|
348
|
+
padding: 0 1;
|
|
349
|
+
color: #9a9a9a;
|
|
350
|
+
border-left: thick #c9a4ff;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.kind-notice {
|
|
354
|
+
padding: 0 0 1 0;
|
|
355
|
+
color: #6a6a6a;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
#queued-strip {
|
|
359
|
+
height: auto;
|
|
360
|
+
max-height: 6;
|
|
361
|
+
padding: 0 1;
|
|
362
|
+
color: #8a8a8a;
|
|
363
|
+
background: #0a0a0a;
|
|
364
|
+
border-top: solid #1e1e1e;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* ── session search and sections ─────────────────────────────────── */
|
|
368
|
+
|
|
369
|
+
#session-search {
|
|
370
|
+
height: 3;
|
|
371
|
+
margin: 0 0 1 0;
|
|
372
|
+
background: #0a0a0a;
|
|
373
|
+
border: solid #1e1e1e;
|
|
374
|
+
color: #c8c8c8;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
#session-search:focus {
|
|
378
|
+
border: solid #333333;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
#session-list > ListItem.session-heading {
|
|
382
|
+
padding: 1 1 0 1;
|
|
383
|
+
color: #5a5a5a;
|
|
384
|
+
background: #000000;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* ── session search and sections ─────────────────────────────────── */
|
|
388
|
+
|
|
389
|
+
#session-search {
|
|
390
|
+
height: 3;
|
|
391
|
+
margin: 0 0 1 0;
|
|
392
|
+
background: #0a0a0a;
|
|
393
|
+
border: solid #1e1e1e;
|
|
394
|
+
color: #c8c8c8;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
#session-search:focus {
|
|
398
|
+
border: solid #333333;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
#session-list > ListItem.session-heading {
|
|
402
|
+
padding: 1 1 0 1;
|
|
403
|
+
color: #5a5a5a;
|
|
404
|
+
background: #000000;
|
|
405
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""A one-line search field with undo and Emacs-style kill and yank.
|
|
2
|
+
|
|
3
|
+
Ctrl+Z undoes the last change. Ctrl+K cuts to the end, Ctrl+U to the start,
|
|
4
|
+
Ctrl+W the word before the cursor; Ctrl+Y puts the last cut back at the
|
|
5
|
+
cursor. Cuts in a row join into one, as a terminal user would expect.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from textual.binding import Binding
|
|
11
|
+
from textual.widgets import Input
|
|
12
|
+
|
|
13
|
+
#: How many earlier values Ctrl+Z can step back through.
|
|
14
|
+
UNDO_DEPTH = 50
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SearchBox(Input):
|
|
18
|
+
"""``Input`` plus undo history and a single-slot kill ring."""
|
|
19
|
+
|
|
20
|
+
BINDINGS = [
|
|
21
|
+
Binding("ctrl+z", "undo", "Undo", show=False),
|
|
22
|
+
Binding("ctrl+k", "kill_to_end", "Cut to end", show=False),
|
|
23
|
+
Binding("ctrl+u", "kill_to_start", "Cut to start", show=False),
|
|
24
|
+
Binding("ctrl+w", "kill_word", "Cut word", show=False),
|
|
25
|
+
Binding("ctrl+y", "yank", "Paste cut", show=False),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
29
|
+
super().__init__(*args, **kwargs)
|
|
30
|
+
self._history: list[str] = []
|
|
31
|
+
self._last = ""
|
|
32
|
+
self._undoing = False
|
|
33
|
+
self.killed = ""
|
|
34
|
+
self._killing = False
|
|
35
|
+
|
|
36
|
+
def on_input_changed(self, event: Input.Changed) -> None:
|
|
37
|
+
# Record the value before each change so Ctrl+Z can return to it.
|
|
38
|
+
if event.input is self and not self._undoing and event.value != self._last:
|
|
39
|
+
self._history.append(self._last)
|
|
40
|
+
del self._history[:-UNDO_DEPTH]
|
|
41
|
+
self._last = event.value
|
|
42
|
+
self._undoing = False
|
|
43
|
+
|
|
44
|
+
def action_undo(self) -> None:
|
|
45
|
+
if self._history:
|
|
46
|
+
self._undoing = True
|
|
47
|
+
self.value = self._history.pop()
|
|
48
|
+
self.cursor_position = len(self.value)
|
|
49
|
+
|
|
50
|
+
def _cut(self, start: int, end: int, *, before: bool) -> None:
|
|
51
|
+
piece = self.value[start:end]
|
|
52
|
+
if not piece:
|
|
53
|
+
return
|
|
54
|
+
# Consecutive cuts build one piece, in reading order.
|
|
55
|
+
if self._killing:
|
|
56
|
+
self.killed = piece + self.killed if before else self.killed + piece
|
|
57
|
+
else:
|
|
58
|
+
self.killed = piece
|
|
59
|
+
self.value = self.value[:start] + self.value[end:]
|
|
60
|
+
self.cursor_position = start
|
|
61
|
+
self._killing = True
|
|
62
|
+
|
|
63
|
+
def action_kill_to_end(self) -> None:
|
|
64
|
+
self._cut(self.cursor_position, len(self.value), before=False)
|
|
65
|
+
|
|
66
|
+
def action_kill_to_start(self) -> None:
|
|
67
|
+
self._cut(0, self.cursor_position, before=True)
|
|
68
|
+
|
|
69
|
+
def action_kill_word(self) -> None:
|
|
70
|
+
end = self.cursor_position
|
|
71
|
+
start = end
|
|
72
|
+
while start and self.value[start - 1] == " ":
|
|
73
|
+
start -= 1
|
|
74
|
+
while start and self.value[start - 1] != " ":
|
|
75
|
+
start -= 1
|
|
76
|
+
self._cut(start, end, before=True)
|
|
77
|
+
|
|
78
|
+
def action_yank(self) -> None:
|
|
79
|
+
if self.killed:
|
|
80
|
+
at = self.cursor_position
|
|
81
|
+
self.value = self.value[:at] + self.killed + self.value[at:]
|
|
82
|
+
self.cursor_position = at + len(self.killed)
|
|
83
|
+
self._killing = False
|
|
84
|
+
|
|
85
|
+
def on_key(self, event) -> None:
|
|
86
|
+
# Any other key ends a run of cuts.
|
|
87
|
+
if event.key not in ("ctrl+k", "ctrl+u", "ctrl+w"):
|
|
88
|
+
self._killing = False
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
__all__ = ["SearchBox", "UNDO_DEPTH"]
|