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,203 @@
1
+ """Register the fleet watcher with the current user's service manager."""
2
+
3
+ import csv
4
+ import hashlib
5
+ import os
6
+ import re
7
+ import subprocess
8
+ import sys
9
+ import tempfile
10
+ from contextlib import redirect_stderr, redirect_stdout
11
+ from pathlib import Path
12
+ from xml.etree.ElementTree import Element, SubElement, tostring
13
+
14
+ from rlmagent.discovery import default_paths
15
+
16
+
17
+ def _command(*args: str, check=True) -> subprocess.CompletedProcess:
18
+ try:
19
+ result = subprocess.run(args, capture_output=True, text=True, errors="replace", timeout=30)
20
+ except subprocess.TimeoutExpired as exc:
21
+ raise ValueError(f"{args[0]} timed out") from exc
22
+ if check and result.returncode:
23
+ raise ValueError((result.stderr or result.stdout).strip() or f"{args[0]} failed")
24
+ return result
25
+
26
+
27
+ def _print_result(result: subprocess.CompletedProcess) -> int:
28
+ if result.stdout:
29
+ print(result.stdout, end="")
30
+ if result.stderr:
31
+ print(result.stderr, end="", file=sys.stderr)
32
+ return result.returncode
33
+
34
+
35
+ def _task_xml(name: str, sid: str, argv: list[str]) -> bytes:
36
+ task = Element("Task", {"version": "1.2", "xmlns": "http://schemas.microsoft.com/windows/2004/02/mit/task"})
37
+ registration = SubElement(task, "RegistrationInfo")
38
+ SubElement(registration, "Description").text = "Recover saved RLM Agent workers when they exit."
39
+ SubElement(registration, "URI").text = f"\\{name}"
40
+ trigger = SubElement(SubElement(task, "Triggers"), "LogonTrigger")
41
+ SubElement(trigger, "UserId").text = sid
42
+ principal = SubElement(SubElement(task, "Principals"), "Principal", id="Watcher")
43
+ SubElement(principal, "UserId").text = sid
44
+ SubElement(principal, "LogonType").text = "InteractiveToken"
45
+ SubElement(principal, "RunLevel").text = "LeastPrivilege"
46
+ settings = SubElement(task, "Settings")
47
+ for key, value in (
48
+ ("MultipleInstancesPolicy", "IgnoreNew"), ("DisallowStartIfOnBatteries", "false"),
49
+ ("StopIfGoingOnBatteries", "false"), ("ExecutionTimeLimit", "PT0S"),
50
+ ):
51
+ SubElement(settings, key).text = value
52
+ retry = SubElement(settings, "RestartOnFailure")
53
+ SubElement(retry, "Interval").text = "PT1M"
54
+ SubElement(retry, "Count").text = "10"
55
+ action = SubElement(SubElement(task, "Actions", Context="Watcher"), "Exec")
56
+ SubElement(action, "Command").text = argv[0]
57
+ SubElement(action, "Arguments").text = subprocess.list2cmdline(argv[1:])
58
+ return tostring(task, encoding="utf-16", xml_declaration=True)
59
+
60
+
61
+ def _windows(action: str, name: str, argv: list[str], home: Path) -> int:
62
+ name = f"RlmAgentFleet-{name}"
63
+ if action == "install":
64
+ identity = _command("whoami.exe", "/user", "/fo", "csv", "/nh")
65
+ rows = list(csv.reader(identity.stdout.splitlines()))
66
+ sid = rows[0][-1] if rows and rows[0] else ""
67
+ if not re.fullmatch(r"S-1(?:-\d+)+", sid):
68
+ raise ValueError("Could not identify the current Windows user")
69
+ home.mkdir(parents=True, exist_ok=True)
70
+ with tempfile.TemporaryDirectory(dir=home) as directory:
71
+ definition = Path(directory) / "fleet.xml"
72
+ definition.write_bytes(_task_xml(name, sid, argv))
73
+ _command("schtasks.exe", "/Create", "/TN", name, "/XML", str(definition), "/F")
74
+ action = "start"
75
+ if action == "uninstall":
76
+ _command("schtasks.exe", "/End", "/TN", name, check=False)
77
+ native = {"start": "/Run", "stop": "/End", "status": "/Query", "uninstall": "/Delete"}
78
+ extra = {"status": ["/FO", "LIST", "/V"], "uninstall": ["/F"]}.get(action, [])
79
+ return _print_result(_command("schtasks.exe", native[action], "/TN", name, *extra, check=False))
80
+
81
+
82
+ def _systemd_unit(argv: list[str]) -> str:
83
+ # systemd expands percent specifiers and dollar variables even in quoted arguments.
84
+ if any(any(ord(char) < 32 for char in arg) for arg in argv):
85
+ raise ValueError("Service paths may not contain control characters")
86
+ quoted = [
87
+ '"' + arg.replace("\\", "\\\\").replace('"', '\\"').replace("%", "%%").replace("$", "$$") + '"'
88
+ for arg in argv
89
+ ]
90
+ return (
91
+ "[Unit]\nDescription=RLM Agent worker fleet watcher\n\n"
92
+ "[Service]\nType=exec\nExecStart=" + " ".join(quoted) + "\n"
93
+ "Restart=on-failure\nRestartSec=5s\nKillMode=process\n\n"
94
+ "[Install]\nWantedBy=default.target\n"
95
+ )
96
+
97
+
98
+ def _linux(action: str, name: str, argv: list[str]) -> int:
99
+ name = f"rlm-agent-fleet-{name}.service"
100
+ config = Path(os.environ.get("XDG_CONFIG_HOME") or Path.home() / ".config")
101
+ unit = config / "systemd" / "user" / name
102
+ manager = ("systemctl", "--user")
103
+ if action == "install":
104
+ definition = _systemd_unit(argv)
105
+ unit.parent.mkdir(parents=True, exist_ok=True)
106
+ with tempfile.TemporaryDirectory(dir=unit.parent) as directory:
107
+ staged = Path(directory) / unit.name
108
+ staged.write_text(definition, encoding="utf-8")
109
+ staged.replace(unit)
110
+ _command(*manager, "daemon-reload")
111
+ _command(*manager, "enable", name)
112
+ action = "restart"
113
+ if action == "uninstall":
114
+ _command(*manager, "disable", "--now", name)
115
+ unit.unlink(missing_ok=True)
116
+ return _print_result(_command(*manager, "daemon-reload", check=False))
117
+ return _print_result(_command(*manager, action, "--no-pager", name, check=False))
118
+
119
+
120
+ def _launchd_plist(label: str, argv: list[str], home: Path) -> bytes:
121
+ import plistlib
122
+
123
+ log = str(home / "fleet-service.log")
124
+ return plistlib.dumps({
125
+ "Label": label,
126
+ "ProgramArguments": argv,
127
+ "RunAtLoad": True,
128
+ # Restart only after a failure, as the other service managers do.
129
+ "KeepAlive": {"SuccessfulExit": False},
130
+ "ThrottleInterval": 5,
131
+ "ProcessType": "Background",
132
+ "StandardOutPath": log,
133
+ "StandardErrorPath": log,
134
+ })
135
+
136
+
137
+ def _macos(action: str, name: str, argv: list[str], home: Path) -> int:
138
+ label = f"com.rlmagent.fleet.{name}"
139
+ agents = Path.home() / "Library" / "LaunchAgents"
140
+ plist = agents / f"{label}.plist"
141
+ domain = f"gui/{os.getuid()}"
142
+ target = f"{domain}/{label}"
143
+ if action == "install":
144
+ home.mkdir(parents=True, exist_ok=True)
145
+ agents.mkdir(parents=True, exist_ok=True)
146
+ with tempfile.TemporaryDirectory(dir=agents) as directory:
147
+ staged = Path(directory) / plist.name
148
+ staged.write_bytes(_launchd_plist(label, argv, home))
149
+ staged.replace(plist)
150
+ # Reinstalling replaces a loaded definition rather than failing on it.
151
+ _command("launchctl", "bootout", target, check=False)
152
+ _command("launchctl", "bootstrap", domain, str(plist))
153
+ return _print_result(_command("launchctl", "kickstart", "-k", target, check=False))
154
+ if action == "uninstall":
155
+ result = _command("launchctl", "bootout", target, check=False)
156
+ plist.unlink(missing_ok=True)
157
+ return _print_result(result)
158
+ if action == "start":
159
+ if not plist.exists():
160
+ raise ValueError("The worker service is not installed; run worker service install")
161
+ _command("launchctl", "bootstrap", domain, str(plist), check=False)
162
+ return _print_result(_command("launchctl", "kickstart", target, check=False))
163
+ if action == "stop":
164
+ return _print_result(_command("launchctl", "bootout", target, check=False))
165
+ return _print_result(_command("launchctl", "print", target, check=False))
166
+
167
+
168
+ def manage(action: str) -> int:
169
+ paths = default_paths()
170
+ home = paths.home.resolve()
171
+ name = hashlib.sha256(os.path.normcase(str(home)).encode()).hexdigest()[:16]
172
+ argv = [sys.executable, "-m", "rlmagent.worker_service", str(home), str(paths.sessions.resolve())]
173
+ if sys.platform == "win32":
174
+ windowless = Path(sys.executable).with_name("pythonw.exe")
175
+ if windowless.is_file():
176
+ argv[0] = str(windowless)
177
+ return _windows(action, name, argv, home)
178
+ if sys.platform == "linux":
179
+ return _linux(action, name, argv)
180
+ if sys.platform == "darwin":
181
+ return _macos(action, name, argv, home)
182
+ raise ValueError("Worker service management needs Windows Task Scheduler, systemd or launchd")
183
+
184
+
185
+ def _run(home: str, sessions: str) -> int:
186
+ from rlmagent.worker import _watch_fleet
187
+
188
+ os.environ["RLM_AGENT_HOME"] = home
189
+ os.environ["RLM_AGENT_SESSIONS_DIR"] = sessions
190
+ if sys.platform != "win32":
191
+ return _watch_fleet()
192
+ executable = Path(sys.executable)
193
+ console = executable.with_name("python.exe")
194
+ if executable.name.lower() == "pythonw.exe" and console.is_file():
195
+ # Workers keep using the console interpreter with their existing detached flags.
196
+ sys.executable = str(console)
197
+ with (Path(home) / "fleet-service.log").open("a", encoding="utf-8", buffering=1) as log:
198
+ with redirect_stdout(log), redirect_stderr(log):
199
+ return _watch_fleet()
200
+
201
+
202
+ if __name__ == "__main__":
203
+ raise SystemExit(_run(*sys.argv[1:]))