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.
Files changed (203) hide show
  1. rlmagent/__init__.py +1 -0
  2. rlmagent/__main__.py +8 -0
  3. rlmagent/acp/__init__.py +1 -0
  4. rlmagent/acp/runtime.py +71 -0
  5. rlmagent/acp/server.py +598 -0
  6. rlmagent/acp/updates.py +128 -0
  7. rlmagent/agentnet.py +237 -0
  8. rlmagent/agents/__init__.py +1 -0
  9. rlmagent/agents/approval.py +90 -0
  10. rlmagent/agents/bridge.py +109 -0
  11. rlmagent/agents/budget.py +212 -0
  12. rlmagent/agents/child_handles.py +46 -0
  13. rlmagent/agents/child_mailbox.py +30 -0
  14. rlmagent/agents/kernel_api.py +156 -0
  15. rlmagent/agents/output.py +34 -0
  16. rlmagent/agents/patch.py +107 -0
  17. rlmagent/agents/prompts.py +156 -0
  18. rlmagent/agents/trace.py +290 -0
  19. rlmagent/agents/tree.py +682 -0
  20. rlmagent/agents/wasm_api.py +105 -0
  21. rlmagent/agents/workspace.py +357 -0
  22. rlmagent/autopilot.py +447 -0
  23. rlmagent/backend.py +946 -0
  24. rlmagent/backend_context.py +74 -0
  25. rlmagent/cli/__init__.py +5 -0
  26. rlmagent/cli/config_cmd.py +177 -0
  27. rlmagent/cli/health.py +156 -0
  28. rlmagent/cli/main.py +1194 -0
  29. rlmagent/cli/update.py +188 -0
  30. rlmagent/clipboard.py +63 -0
  31. rlmagent/config/__init__.py +37 -0
  32. rlmagent/config/config.py +297 -0
  33. rlmagent/config/loader.py +119 -0
  34. rlmagent/config/prompts.py +124 -0
  35. rlmagent/config/secure_input.py +213 -0
  36. rlmagent/config/store.py +275 -0
  37. rlmagent/context/__init__.py +47 -0
  38. rlmagent/context/budget.py +557 -0
  39. rlmagent/contract.py +311 -0
  40. rlmagent/contract_run.py +232 -0
  41. rlmagent/conversation.py +2547 -0
  42. rlmagent/cron.py +77 -0
  43. rlmagent/directives.py +893 -0
  44. rlmagent/discovery.py +356 -0
  45. rlmagent/execution_log.py +66 -0
  46. rlmagent/factory.py +189 -0
  47. rlmagent/factory_graph.py +413 -0
  48. rlmagent/harness/__init__.py +63 -0
  49. rlmagent/harness/contracts/__init__.py +1 -0
  50. rlmagent/harness/contracts/stream.py +129 -0
  51. rlmagent/harness/contracts/tooling.py +127 -0
  52. rlmagent/harness/contracts/transcript/__init__.py +68 -0
  53. rlmagent/harness/contracts/transcript/_base.py +29 -0
  54. rlmagent/harness/contracts/transcript/diagnostics.py +52 -0
  55. rlmagent/harness/contracts/transcript/entries.py +180 -0
  56. rlmagent/harness/contracts/transcript/segments.py +64 -0
  57. rlmagent/harness/contracts/values.py +6 -0
  58. rlmagent/harness/driver.py +397 -0
  59. rlmagent/harness/engine.py +489 -0
  60. rlmagent/harness/mending.py +153 -0
  61. rlmagent/harness/provider/__init__.py +1 -0
  62. rlmagent/harness/provider/base.py +27 -0
  63. rlmagent/harness/provider/wire.py +189 -0
  64. rlmagent/harness/py.typed +0 -0
  65. rlmagent/harness/session/__init__.py +5 -0
  66. rlmagent/harness/session/index.py +218 -0
  67. rlmagent/harness/session/lease.py +66 -0
  68. rlmagent/harness/session/records.py +118 -0
  69. rlmagent/harness/session/replay.py +165 -0
  70. rlmagent/harness/session/store.py +155 -0
  71. rlmagent/harness/session/summary.py +46 -0
  72. rlmagent/hints.py +47 -0
  73. rlmagent/history.py +92 -0
  74. rlmagent/hooks/__init__.py +1 -0
  75. rlmagent/hooks/hooks.py +1 -0
  76. rlmagent/instructions.py +157 -0
  77. rlmagent/job_runner.py +117 -0
  78. rlmagent/jobs.py +365 -0
  79. rlmagent/kept_agents.py +397 -0
  80. rlmagent/kernel/__init__.py +5 -0
  81. rlmagent/kernel/session.py +205 -0
  82. rlmagent/kernel/wasm.py +169 -0
  83. rlmagent/logs.py +208 -0
  84. rlmagent/managed_release.py +368 -0
  85. rlmagent/mcp.py +411 -0
  86. rlmagent/mcp_oauth.py +213 -0
  87. rlmagent/model/__init__.py +29 -0
  88. rlmagent/model/_claude/__init__.py +1 -0
  89. rlmagent/model/_claude/courier.py +117 -0
  90. rlmagent/model/_claude/emitter.py +378 -0
  91. rlmagent/model/_oai/__init__.py +1 -0
  92. rlmagent/model/_oai/helpers.py +256 -0
  93. rlmagent/model/_oai/normalize.py +263 -0
  94. rlmagent/model/_oai/parsers.py +317 -0
  95. rlmagent/model/_oai/payloads.py +226 -0
  96. rlmagent/model/_oai/transport.py +127 -0
  97. rlmagent/model/claude.py +552 -0
  98. rlmagent/model/copilot.py +142 -0
  99. rlmagent/model/correlation.py +21 -0
  100. rlmagent/model/deadline.py +19 -0
  101. rlmagent/model/discovery.py +181 -0
  102. rlmagent/model/external/__init__.py +11 -0
  103. rlmagent/model/external/acp.py +202 -0
  104. rlmagent/model/external/acp_install.py +109 -0
  105. rlmagent/model/external/cli.py +248 -0
  106. rlmagent/model/external/textcalls.py +57 -0
  107. rlmagent/model/failover.py +129 -0
  108. rlmagent/model/google.py +199 -0
  109. rlmagent/model/limits.py +46 -0
  110. rlmagent/model/oai_compatible.py +414 -0
  111. rlmagent/model/preferences.py +56 -0
  112. rlmagent/model/py.typed +0 -0
  113. rlmagent/model/registered_oauth.py +195 -0
  114. rlmagent/model/scripted.py +55 -0
  115. rlmagent/model/settings.py +271 -0
  116. rlmagent/model/subscription.py +398 -0
  117. rlmagent/model/token_bounds.py +15 -0
  118. rlmagent/model/transport/__init__.py +1 -0
  119. rlmagent/model/transport/backoff.py +87 -0
  120. rlmagent/model/transport/client.py +87 -0
  121. rlmagent/model/transport/faults.py +65 -0
  122. rlmagent/model/transport/responses_socket.py +223 -0
  123. rlmagent/model/vertex.py +93 -0
  124. rlmagent/network.py +123 -0
  125. rlmagent/packages.py +246 -0
  126. rlmagent/planning.py +84 -0
  127. rlmagent/playbook/__init__.py +1 -0
  128. rlmagent/playbook/apply.py +106 -0
  129. rlmagent/playbook/digest.py +63 -0
  130. rlmagent/playbook/improver.py +272 -0
  131. rlmagent/playbook/plan.py +99 -0
  132. rlmagent/playbook/store.py +67 -0
  133. rlmagent/plugins.py +327 -0
  134. rlmagent/policy.py +105 -0
  135. rlmagent/prompt_refs.py +57 -0
  136. rlmagent/prompts.py +331 -0
  137. rlmagent/prune.py +60 -0
  138. rlmagent/py.typed +0 -0
  139. rlmagent/reasoning.py +135 -0
  140. rlmagent/refinement/__init__.py +1 -0
  141. rlmagent/refinement/controller.py +448 -0
  142. rlmagent/refinement/engine.py +220 -0
  143. rlmagent/refinement/evidence.py +131 -0
  144. rlmagent/refinement/scoped.py +90 -0
  145. rlmagent/refinement/store.py +193 -0
  146. rlmagent/refresh.py +202 -0
  147. rlmagent/routing.py +52 -0
  148. rlmagent/runtime.py +575 -0
  149. rlmagent/safety.py +267 -0
  150. rlmagent/session_html.py +57 -0
  151. rlmagent/session_import.py +52 -0
  152. rlmagent/session_share.py +55 -0
  153. rlmagent/sessions.py +249 -0
  154. rlmagent/shell_sandbox.py +88 -0
  155. rlmagent/skillset.py +320 -0
  156. rlmagent/steering.py +148 -0
  157. rlmagent/stops.py +62 -0
  158. rlmagent/subscriptions.py +219 -0
  159. rlmagent/tasks.py +98 -0
  160. rlmagent/toolhost.py +310 -0
  161. rlmagent/tools/__init__.py +56 -0
  162. rlmagent/tools/_shared.py +210 -0
  163. rlmagent/tools/files.py +462 -0
  164. rlmagent/tools/kernel.py +93 -0
  165. rlmagent/tools/patch_tool.py +68 -0
  166. rlmagent/tools/search.py +188 -0
  167. rlmagent/trust.py +383 -0
  168. rlmagent/tui/__init__.py +1 -0
  169. rlmagent/tui/adapter.py +506 -0
  170. rlmagent/tui/app.py +711 -0
  171. rlmagent/tui/approval_screen.py +57 -0
  172. rlmagent/tui/choice_screen.py +47 -0
  173. rlmagent/tui/completions.py +116 -0
  174. rlmagent/tui/components.py +25 -0
  175. rlmagent/tui/composer.py +40 -0
  176. rlmagent/tui/factory_view.py +86 -0
  177. rlmagent/tui/inspect.py +193 -0
  178. rlmagent/tui/messages.py +215 -0
  179. rlmagent/tui/model_screen.py +307 -0
  180. rlmagent/tui/prompt.py +98 -0
  181. rlmagent/tui/question_screen.py +52 -0
  182. rlmagent/tui/replay.py +141 -0
  183. rlmagent/tui/rlm_agent.tcss +405 -0
  184. rlmagent/tui/search_box.py +91 -0
  185. rlmagent/tui/sidebar.py +182 -0
  186. rlmagent/tui/status.py +49 -0
  187. rlmagent/tui/steps.py +283 -0
  188. rlmagent/tui/suggest.py +778 -0
  189. rlmagent/tui/transcript.py +172 -0
  190. rlmagent/tui/trust_dialog.py +58 -0
  191. rlmagent/tui/worker_attach.py +141 -0
  192. rlmagent/ui/__init__.py +1 -0
  193. rlmagent/ui/render.py +211 -0
  194. rlmagent/undo.py +109 -0
  195. rlmagent/utils/__init__.py +1 -0
  196. rlmagent/websearch.py +75 -0
  197. rlmagent/worker.py +1275 -0
  198. rlmagent/worker_service.py +203 -0
  199. rlmness_cli-0.2.0.dist-info/METADATA +882 -0
  200. rlmness_cli-0.2.0.dist-info/RECORD +203 -0
  201. rlmness_cli-0.2.0.dist-info/WHEEL +4 -0
  202. rlmness_cli-0.2.0.dist-info/entry_points.txt +2 -0
  203. rlmness_cli-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,275 @@
1
+ """Persisted user configuration: schema, file location, and TOML read/write."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import tomllib
8
+ from dataclasses import dataclass, field
9
+ from pathlib import Path
10
+
11
+ # ---------------------------------------------------------------------------
12
+ # Provider metadata (plain data — the set of backends rlm-agent can talk to)
13
+ # ---------------------------------------------------------------------------
14
+
15
+
16
+ @dataclass(frozen=True, slots=True)
17
+ class ProviderInfo:
18
+ """Static facts about a supported provider backend."""
19
+
20
+ key: str
21
+ label: str
22
+ key_env: str
23
+ base_url: str
24
+ models: tuple[str, ...]
25
+ needs_key: bool = True
26
+
27
+
28
+ PROVIDERS: tuple[ProviderInfo, ...] = (
29
+ ProviderInfo(key="copilot", label="GitHub Copilot", key_env="COPILOT_GITHUB_TOKEN",
30
+ base_url="", models=("auto",), needs_key=False),
31
+ ProviderInfo(key="claude-subscription", label="Claude Code subscription", key_env="",
32
+ base_url="", models=("cli:claude-code",), needs_key=False),
33
+ ProviderInfo(key="xai", label="xAI", key_env="XAI_API_KEY",
34
+ base_url="https://api.x.ai/v1", models=("grok-4",)),
35
+ ProviderInfo(key="xai-subscription", label="xAI registered OAuth", key_env="",
36
+ base_url="https://api.x.ai/v1", models=("grok-4",), needs_key=False),
37
+ ProviderInfo(key="codex", label="Codex subscription", key_env="",
38
+ base_url="https://api.openai.com/v1", models=("gpt-6.1-sol",), needs_key=False),
39
+ ProviderInfo(key="google", label="Google Gemini", key_env="GOOGLE_API_KEY",
40
+ base_url="https://generativelanguage.googleapis.com/v1beta", models=("gemini-2.5-flash",)),
41
+ ProviderInfo(key="mistral", label="Mistral", key_env="MISTRAL_API_KEY",
42
+ base_url="https://api.mistral.ai/v1", models=("mistral-small-latest",)),
43
+ ProviderInfo(key="azure", label="Azure OpenAI", key_env="AZURE_OPENAI_API_KEY",
44
+ base_url="", models=("deployment-name",)),
45
+ ProviderInfo(
46
+ key="openai",
47
+ label="OpenAI",
48
+ key_env="OPENAI_API_KEY",
49
+ base_url="https://api.openai.com/v1",
50
+ models=("gpt-5", "gpt-5-mini", "gpt-4.1"),
51
+ ),
52
+ ProviderInfo(
53
+ key="anthropic",
54
+ label="Anthropic",
55
+ key_env="ANTHROPIC_API_KEY",
56
+ base_url="https://api.anthropic.com",
57
+ models=("claude-opus-5", "claude-sonnet-5", "claude-haiku-4-5"),
58
+ ),
59
+ ProviderInfo(
60
+ key="openrouter",
61
+ label="OpenRouter",
62
+ key_env="OPENROUTER_API_KEY",
63
+ base_url="https://openrouter.ai/api/v1",
64
+ models=(
65
+ "anthropic/claude-opus-5",
66
+ "openai/gpt-5",
67
+ "meta-llama/llama-3.3-70b-instruct",
68
+ ),
69
+ ),
70
+ ProviderInfo(
71
+ key="ollama",
72
+ label="Ollama",
73
+ key_env="OLLAMA_API_KEY",
74
+ base_url="http://localhost:11434/v1",
75
+ models=("llama3.3", "qwen2.5-coder", "deepseek-r1"),
76
+ needs_key=False,
77
+ ),
78
+ ProviderInfo(
79
+ key="huggingface",
80
+ label="Hugging Face",
81
+ key_env="HF_TOKEN",
82
+ base_url="https://router.huggingface.co/v1",
83
+ models=(
84
+ "openai/gpt-oss-120b",
85
+ "Qwen/Qwen3-Coder-480B-A35B-Instruct",
86
+ "moonshotai/Kimi-K2-Instruct",
87
+ ),
88
+ ),
89
+ ProviderInfo(key="vertex", label="Google Vertex", key_env="GOOGLE_CLOUD_PROJECT",
90
+ base_url="", models=("google/gemini-3.5-flash",), needs_key=False),
91
+ )
92
+
93
+
94
+ def provider_info(key: str) -> ProviderInfo:
95
+ """Look up provider metadata by key, raising ``KeyError`` when unknown."""
96
+ for info in PROVIDERS:
97
+ if info.key == key:
98
+ return info
99
+ raise KeyError(key)
100
+
101
+
102
+ PROVIDER_KEYS: tuple[str, ...] = tuple(p.key for p in PROVIDERS)
103
+
104
+
105
+ # ---------------------------------------------------------------------------
106
+ # Config file schema
107
+ # ---------------------------------------------------------------------------
108
+
109
+
110
+ @dataclass(slots=True)
111
+ class RlmAgentConfig:
112
+ """Contents of the user's ``config.toml``."""
113
+
114
+ provider: str | None = None
115
+ model: str | None = None
116
+ api_keys: dict[str, str] = field(default_factory=dict)
117
+ base_urls: dict[str, str] = field(default_factory=dict)
118
+ # How folders with no saved trust decision are treated: ask, always, never.
119
+ # Read only from this user-wide file, never from a project.
120
+ project_trust: str | None = None
121
+
122
+
123
+ # ---------------------------------------------------------------------------
124
+ # Location
125
+ # ---------------------------------------------------------------------------
126
+
127
+
128
+ MAX_RECENT = 8
129
+
130
+
131
+ def recent_models() -> list[dict]:
132
+ """Models chosen before, newest first: [{"provider", "model"}]. Holds no keys."""
133
+ try:
134
+ saved = json.loads((config_dir() / "recent_models.json").read_text(encoding="utf-8"))
135
+ except (OSError, ValueError):
136
+ return []
137
+ return [r for r in saved if isinstance(r, dict) and r.get("provider") and r.get("model")]
138
+
139
+
140
+ def remember_model(provider: str, model: str) -> None:
141
+ entry = {"provider": provider, "model": model}
142
+ kept = [entry] + [r for r in recent_models() if r != entry]
143
+ config_dir().mkdir(parents=True, exist_ok=True)
144
+ (config_dir() / "recent_models.json").write_text(
145
+ json.dumps(kept[:MAX_RECENT], indent=2), encoding="utf-8")
146
+
147
+
148
+ def forget_model(provider: str, model: str) -> None:
149
+ kept = [r for r in recent_models() if r != {"provider": provider, "model": model}]
150
+ (config_dir() / "recent_models.json").write_text(json.dumps(kept, indent=2), encoding="utf-8")
151
+
152
+
153
+ def config_dir() -> Path:
154
+ """Directory holding rlm-agent's user configuration."""
155
+ override = os.environ.get("RLM_AGENT_CONFIG_DIR") or os.environ.get("RLM_AGENT_HOME")
156
+ if override:
157
+ return Path(override)
158
+ return Path.home() / ".rlm-agent"
159
+
160
+
161
+ def config_path() -> Path:
162
+ """Full path to ``config.toml``."""
163
+ return config_dir() / "config.toml"
164
+
165
+
166
+ # ---------------------------------------------------------------------------
167
+ # Read
168
+ # ---------------------------------------------------------------------------
169
+
170
+
171
+ def load_config() -> RlmAgentConfig:
172
+ """Read the config file, returning empty defaults when absent or malformed."""
173
+ path = config_path()
174
+ try:
175
+ raw = path.read_bytes()
176
+ except OSError:
177
+ return RlmAgentConfig()
178
+
179
+ try:
180
+ data = tomllib.loads(raw.decode("utf-8"))
181
+ except (tomllib.TOMLDecodeError, UnicodeDecodeError):
182
+ return RlmAgentConfig()
183
+
184
+ providers = data.get("providers")
185
+ api_keys: dict[str, str] = {}
186
+ base_urls: dict[str, str] = {}
187
+ if isinstance(providers, dict):
188
+ for name, section in providers.items():
189
+ if not isinstance(section, dict):
190
+ continue
191
+ key = section.get("api_key")
192
+ if isinstance(key, str) and key:
193
+ api_keys[name] = key
194
+ url = section.get("base_url")
195
+ if isinstance(url, str) and url:
196
+ base_urls[name] = url
197
+
198
+ provider = data.get("provider")
199
+ model = data.get("model")
200
+ trust = data.get("project_trust")
201
+ return RlmAgentConfig(
202
+ provider=provider if isinstance(provider, str) and provider else None,
203
+ model=model if isinstance(model, str) and model else None,
204
+ api_keys=api_keys,
205
+ base_urls=base_urls,
206
+ project_trust=trust if trust in ("ask", "always", "never") else None,
207
+ )
208
+
209
+
210
+ # ---------------------------------------------------------------------------
211
+ # Write
212
+ # ---------------------------------------------------------------------------
213
+
214
+
215
+ def _quote(value: str) -> str:
216
+ """Serialize a string as a TOML basic string."""
217
+ escaped = value.replace("\\", "\\\\").replace('"', '\\"')
218
+ return f'"{escaped}"'
219
+
220
+
221
+ def render_config(config: RlmAgentConfig) -> str:
222
+ """Render *config* as TOML text."""
223
+ lines: list[str] = []
224
+ if config.provider:
225
+ lines.append(f"provider = {_quote(config.provider)}")
226
+ if config.model:
227
+ lines.append(f"model = {_quote(config.model)}")
228
+ if config.project_trust:
229
+ lines.append(f"project_trust = {_quote(config.project_trust)}")
230
+
231
+ names = sorted(set(config.api_keys) | set(config.base_urls))
232
+ for name in names:
233
+ lines.append("")
234
+ lines.append(f"[providers.{name}]")
235
+ if name in config.api_keys:
236
+ lines.append(f"api_key = {_quote(config.api_keys[name])}")
237
+ if name in config.base_urls:
238
+ lines.append(f"base_url = {_quote(config.base_urls[name])}")
239
+ return "\n".join(lines) + "\n"
240
+
241
+
242
+ def save_config(config: RlmAgentConfig) -> Path:
243
+ """Write *config* to disk with owner-only permissions."""
244
+ path = config_path()
245
+ path.parent.mkdir(parents=True, exist_ok=True)
246
+ path.write_text(render_config(config), encoding="utf-8")
247
+ try:
248
+ path.chmod(0o600)
249
+ except OSError:
250
+ pass # best effort — some filesystems (e.g. FAT) reject chmod
251
+ return path
252
+
253
+
254
+ def mask_key(value: str) -> str:
255
+ """Render an API key safe for display: last four characters only."""
256
+ if not value:
257
+ return ""
258
+ if len(value) <= 4:
259
+ return "*" * len(value)
260
+ return "*" * (len(value) - 4) + value[-4:]
261
+
262
+
263
+ __all__ = [
264
+ "RlmAgentConfig",
265
+ "PROVIDERS",
266
+ "PROVIDER_KEYS",
267
+ "ProviderInfo",
268
+ "config_dir",
269
+ "config_path",
270
+ "load_config",
271
+ "mask_key",
272
+ "provider_info",
273
+ "render_config",
274
+ "save_config",
275
+ ]
@@ -0,0 +1,47 @@
1
+ """rlm-agent context: token budgeting, threshold detection, and compaction planning."""
2
+
3
+ from rlmagent.context.budget import (
4
+ CHARS_PER_TOKEN,
5
+ DEFAULT_SUMMARY_SYSTEM,
6
+ DEFAULT_WINDOW,
7
+ MODEL_WINDOWS,
8
+ TOOL_SCHEMA_BASELINE,
9
+ CompactionPlan,
10
+ CompactionResult,
11
+ ContextEstimate,
12
+ ContextLimits,
13
+ apply_compaction,
14
+ build_summary_prompts,
15
+ estimate_context,
16
+ estimate_entry_tokens,
17
+ estimate_system_tokens,
18
+ estimate_tool_tokens,
19
+ estimate_transcript_tokens,
20
+ exceeds_threshold,
21
+ plan_compaction,
22
+ resolve_window,
23
+ serialize_for_summary,
24
+ )
25
+
26
+ __all__ = [
27
+ "CHARS_PER_TOKEN",
28
+ "CompactionPlan",
29
+ "CompactionResult",
30
+ "ContextEstimate",
31
+ "ContextLimits",
32
+ "DEFAULT_SUMMARY_SYSTEM",
33
+ "DEFAULT_WINDOW",
34
+ "MODEL_WINDOWS",
35
+ "TOOL_SCHEMA_BASELINE",
36
+ "apply_compaction",
37
+ "build_summary_prompts",
38
+ "estimate_context",
39
+ "estimate_entry_tokens",
40
+ "estimate_system_tokens",
41
+ "estimate_tool_tokens",
42
+ "estimate_transcript_tokens",
43
+ "exceeds_threshold",
44
+ "plan_compaction",
45
+ "resolve_window",
46
+ "serialize_for_summary",
47
+ ]