relay-codex 0.0.0-stage → 0.5.0

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.
@@ -0,0 +1,350 @@
1
+ """Local management and native-option behavior, with isolated state."""
2
+
3
+ import asyncio
4
+ import contextlib
5
+ import io
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ import plistlib
10
+ import subprocess
11
+ import sys
12
+ import tempfile
13
+ import time
14
+ import unittest
15
+ from unittest.mock import patch
16
+
17
+ from launch import backend_arguments, native_mode, prepare_worktree, translate
18
+ from pool import Pool
19
+ from router import parse_arguments
20
+ from settings import atomic_json
21
+ import commands
22
+ import desktop
23
+ import execution
24
+ import tools
25
+
26
+
27
+ class ManagementTests(unittest.TestCase):
28
+ def test_profile_layer_and_explicit_config_precedence(self):
29
+ with tempfile.TemporaryDirectory() as folder:
30
+ home = Path(folder)
31
+ (home / "work.config.toml").write_text(
32
+ 'model="profile-model"\n[features]\nexample=true\n'
33
+ )
34
+ with patch.dict(os.environ, {"CODEX_HOME": folder}):
35
+ args = backend_arguments(["-pwork", "-c", 'model="explicit-model"'])
36
+ self.assertEqual(args[-2:], ["-c", 'model="explicit-model"'])
37
+ self.assertIn('model="profile-model"', args)
38
+ self.assertIn("features.example=true", args)
39
+ self.assertEqual(
40
+ translate(["resume", "-pwork", "--add-dir", "/tmp"])[0], ["resume"]
41
+ )
42
+ with self.assertRaises(ValueError):
43
+ backend_arguments(["-p", "../outside"])
44
+ self.assertFalse(native_mode(["resume", "-pwork", "--add-dir=/tmp"]))
45
+
46
+ def test_background_cd_resolves_against_supplied_directory(self):
47
+ with tempfile.TemporaryDirectory() as folder:
48
+ root = Path(folder).resolve()
49
+ (root / "child").mkdir()
50
+ cwd, settings, backend = __import__("service").headless_options(
51
+ ["-Cchild", "-mtest", "-c", "features.x=true"], str(root)
52
+ )
53
+ self.assertEqual(cwd, str(root / "child"))
54
+ self.assertEqual(settings["model"], "test")
55
+ self.assertEqual(backend[-2:], ["-c", "features.x=true"])
56
+
57
+ def test_real_git_worktree_is_separate_and_resume_combination_is_rejected(self):
58
+ with tempfile.TemporaryDirectory() as folder:
59
+ root = Path(folder).resolve()
60
+ repo = root / "repo"
61
+ state = root / "state"
62
+ repo.mkdir()
63
+ state.mkdir()
64
+ subprocess.run(["git", "init", "-q", str(repo)], check=True)
65
+ subprocess.run(
66
+ [
67
+ "git",
68
+ "-C",
69
+ str(repo),
70
+ "-c",
71
+ "user.name=Test",
72
+ "-c",
73
+ "user.email=test@example.invalid",
74
+ "commit",
75
+ "--allow-empty",
76
+ "-qm",
77
+ "test",
78
+ ],
79
+ check=True,
80
+ )
81
+ args, cwd = prepare_worktree(["--worktree", "hello"], state, str(repo))
82
+ self.assertNotEqual(cwd, str(repo))
83
+ self.assertTrue((Path(cwd) / ".git").exists())
84
+ self.assertNotIn("--worktree", args)
85
+ with self.assertRaises(ValueError):
86
+ prepare_worktree(["resume", "--worktree"], state, str(repo))
87
+
88
+ def test_account_emails_are_displayed_and_resolve_without_reading_tokens(self):
89
+ with tempfile.TemporaryDirectory() as folder:
90
+ state = Path(folder)
91
+ home = state / "accounts" / "account1"
92
+ home.mkdir(parents=True)
93
+ atomic_json(home / "enrolled", {"identity": "synthetic"})
94
+ atomic_json(home / "identity.json", {"email": "person@example.invalid"})
95
+ with patch(
96
+ "router.read_keychain",
97
+ side_effect=AssertionError("Status must not read Keychain"),
98
+ ):
99
+ result = commands.snapshot(state)
100
+ self.assertEqual(
101
+ result["accounts"][0]["name"], "person@example.invalid"
102
+ )
103
+ self.assertEqual(
104
+ commands.resolve_label(state, "person@example.invalid"), "account1"
105
+ )
106
+
107
+ def test_redacted_diagnostics_omit_paths_names_and_prompts(self):
108
+ with tempfile.TemporaryDirectory() as folder:
109
+ state = Path(folder)
110
+ pool = Pool(state)
111
+ pool.assign("private-label")
112
+ pool.touch(
113
+ cwd="/secret/project",
114
+ name="secret-task",
115
+ state="working",
116
+ prompt="PRIVATE_PROMPT",
117
+ )
118
+ pool.record("working", project="/secret/project", email="PRIVATE_EMAIL")
119
+ target = state / "diagnostics.json"
120
+ tools.diagnostics(state, target)
121
+ output = target.read_text()
122
+ for secret in [
123
+ "/secret/project",
124
+ "private-label",
125
+ "secret-task",
126
+ "PRIVATE_PROMPT",
127
+ "PRIVATE_EMAIL",
128
+ folder,
129
+ ]:
130
+ self.assertNotIn(secret, output)
131
+ self.assertEqual(target.stat().st_mode & 0o777, 0o600)
132
+ pool.close()
133
+
134
+ def test_cleanup_dry_run_retains_workspace_and_runtime_references(self):
135
+ with tempfile.TemporaryDirectory() as folder:
136
+ state = Path(folder)
137
+ old = state / "native-runtimes" / "old"
138
+ old.mkdir(parents=True)
139
+ (old / "binary").write_text("test")
140
+ unused = state / "relay-runtimes" / "unused"
141
+ unused.mkdir(parents=True)
142
+ job = state / "services" / "saved.job.json"
143
+ atomic_json(job, {"state": "stopped", "binary": str(old / "binary")})
144
+ atomic_json(state / "workspaces.json", {"daily": ["saved"]})
145
+ before = time.time() - 90 * 86400
146
+ for p in [old, unused, job]:
147
+ os.utime(p, (before, before))
148
+ value = tools.cleanup(state, days=30, jobs=True)
149
+ self.assertTrue(value["dry_run"])
150
+ self.assertEqual(value["paths"], [str(unused)])
151
+ self.assertTrue(unused.exists())
152
+ self.assertTrue(job.exists())
153
+
154
+ def test_launch_agent_uses_stable_app_and_has_no_auth_token(self):
155
+ with tempfile.TemporaryDirectory() as folder:
156
+ state = Path(folder) / "state"
157
+ with patch(
158
+ "subprocess.run", return_value=subprocess.CompletedProcess([], 0)
159
+ ):
160
+ path = desktop.login_item(state, enabled=True, home=folder)
161
+ data = plistlib.loads(path.read_bytes())
162
+ self.assertIn("RelayMenu", data["ProgramArguments"][0])
163
+ self.assertTrue(data["RunAtLoad"])
164
+ self.assertNotIn("token", path.read_text().lower())
165
+ desktop.login_item(state, enabled=False, home=folder)
166
+ self.assertFalse(path.exists())
167
+
168
+ @unittest.skipUnless(sys.platform == "darwin", "Native app bundle test")
169
+ def test_native_menu_bundle_signs_and_symbols_exist(self):
170
+ with tempfile.TemporaryDirectory() as folder:
171
+ app = desktop.menubar(
172
+ Path(folder), launcher="/usr/bin/false", build_only=True
173
+ )
174
+ verify = subprocess.run(
175
+ ["/usr/bin/codesign", "--verify", "--strict", str(app)],
176
+ capture_output=True,
177
+ )
178
+ self.assertEqual(verify.returncode, 0, verify.stderr.decode())
179
+ result = subprocess.run(
180
+ [str(app / "Contents/MacOS/RelayMenu"), "--self-check"],
181
+ capture_output=True,
182
+ text=True,
183
+ )
184
+ self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
185
+
186
+ def test_secret_profile_fields_do_not_enter_process_arguments_or_pool_settings(
187
+ self,
188
+ ):
189
+ from launch import profile_config
190
+ from router import Controller
191
+ from test_router import FakeAccount, FakeBackend
192
+
193
+ with tempfile.TemporaryDirectory() as folder:
194
+ home = Path(folder)
195
+ (home / "private.config.toml").write_text(
196
+ 'model="normal"\n[mcp_servers.example.http_headers]\nAuthorization="PRIVATE_TEST_SECRET"\n'
197
+ )
198
+ with patch.dict(os.environ, {"CODEX_HOME": folder}):
199
+ arguments = backend_arguments(["-p", "private"])
200
+ self.assertNotIn("PRIVATE_TEST_SECRET", " ".join(arguments))
201
+ overrides = profile_config(["-p", "private"])
202
+ self.assertIn("PRIVATE_TEST_SECRET", json.dumps(overrides))
203
+ c = Controller(
204
+ [FakeAccount("one")], FakeBackend(), report=lambda _: None
205
+ )
206
+ c.merge_settings("thread", {"config": overrides})
207
+ self.assertNotIn("PRIVATE_TEST_SECRET", json.dumps(c.thread_settings))
208
+
209
+ def test_help_command_and_combined_run_help(self):
210
+ import router
211
+
212
+ for args in [
213
+ [],
214
+ ["help"],
215
+ ["help", "workspace"],
216
+ ["help", "run"],
217
+ ["run", "--help"],
218
+ ]:
219
+ with (
220
+ self.subTest(args=args),
221
+ patch.object(sys, "argv", ["relay", *args]),
222
+ patch("router.codex_path", return_value="/codex"),
223
+ patch("subprocess.call", return_value=0),
224
+ contextlib.redirect_stdout(io.StringIO()) as out,
225
+ ):
226
+ if args in (["help", "run"], ["run", "--help"]):
227
+ self.assertEqual(router.main(), 0)
228
+ self.assertIn("Relay launch options", out.getvalue())
229
+ else:
230
+ with self.assertRaises(SystemExit) as end:
231
+ router.main()
232
+ self.assertEqual(end.exception.code, 0)
233
+ self.assertNotIn("router.py", out.getvalue())
234
+
235
+
236
+ class ExecutionTests(unittest.IsolatedAsyncioTestCase):
237
+ async def test_managed_exec_json_and_last_message_over_real_jsonl(self):
238
+ from test_services import fake_executable
239
+ from test_router import FakeAccount
240
+
241
+ with tempfile.TemporaryDirectory() as folder:
242
+ root = Path(folder)
243
+ home = root / "home"
244
+ home.mkdir()
245
+ state = root / "state"
246
+ state.mkdir()
247
+ for label in ("one", "two"):
248
+ account = state / "accounts" / label
249
+ account.mkdir(parents=True)
250
+ (account / "enrolled").write_text("{}")
251
+ binary = fake_executable(root)
252
+ output_file = root / "answer.txt"
253
+ args = parse_arguments(
254
+ [
255
+ "run",
256
+ "--",
257
+ "exec",
258
+ "--skip-git-repo-check",
259
+ "--json",
260
+ "-o",
261
+ str(output_file),
262
+ "test only",
263
+ ]
264
+ )
265
+
266
+ class Account(FakeAccount):
267
+ def __init__(self, label, *args):
268
+ super().__init__(label)
269
+
270
+ output = io.StringIO()
271
+ with (
272
+ patch.dict(os.environ, {"CODEX_HOME": str(home)}),
273
+ patch("broker.ClientAccount", Account),
274
+ patch("sys.stdin", io.StringIO("")),
275
+ contextlib.redirect_stdout(output),
276
+ ):
277
+ self.assertEqual(
278
+ await asyncio.wait_for(execution.run(args, state, binary), 8), 0
279
+ )
280
+ events = [json.loads(line) for line in output.getvalue().splitlines()]
281
+ self.assertEqual(events[0]["type"], "thread.started")
282
+ self.assertEqual(events[-1]["type"], "turn.completed")
283
+ self.assertEqual(output_file.read_text(), "FAKE_OK\n")
284
+ history = json.loads((home / "fake-history.json").read_text())
285
+ self.assertEqual(
286
+ sum(call["method"] == "turn/start" for call in history["calls"]), 1
287
+ )
288
+
289
+ async def test_noninteractive_approval_is_not_automatically_accepted(self):
290
+ output = execution.Output(True)
291
+ with contextlib.redirect_stderr(io.StringIO()):
292
+ await output.send(
293
+ json.dumps(
294
+ {
295
+ "id": 1,
296
+ "method": "item/commandExecution/requestApproval",
297
+ "params": {},
298
+ }
299
+ )
300
+ )
301
+ self.assertTrue(output.done.is_set())
302
+ self.assertEqual(output.status, 1)
303
+
304
+ async def test_exec_json_includes_tool_records(self):
305
+ output = execution.Output(True)
306
+ output.thread = "parent"
307
+ stream = io.StringIO()
308
+ with contextlib.redirect_stdout(stream):
309
+ for kind in ("mcpToolCall", "webSearch", "collabAgentToolCall"):
310
+ await output.send(
311
+ json.dumps(
312
+ {
313
+ "method": "item/completed",
314
+ "params": {
315
+ "threadId": "parent",
316
+ "item": {"id": kind, "type": kind},
317
+ },
318
+ }
319
+ )
320
+ )
321
+ self.assertEqual(
322
+ [
323
+ json.loads(line)["item"]["type"]
324
+ for line in stream.getvalue().splitlines()
325
+ ],
326
+ ["mcp_tool_call", "web_search", "collab_tool_call"],
327
+ )
328
+
329
+ async def test_only_foreground_thread_can_finish_exec(self):
330
+ output = execution.Output(True)
331
+ output.thread = "parent"
332
+ await output.send(
333
+ json.dumps(
334
+ {
335
+ "method": "turn/completed",
336
+ "params": {"threadId": "child", "turn": {"status": "completed"}},
337
+ }
338
+ )
339
+ )
340
+ self.assertFalse(output.done.is_set())
341
+
342
+ def test_native_exec_subcommands_and_prompt_separator(self):
343
+ value = execution.options(
344
+ ["exec", "resume", "saved", "--json", "--", "--yolo"], "/tmp"
345
+ )
346
+ self.assertEqual(value["mode"], "resume")
347
+ self.assertEqual(value["target"], "saved")
348
+ self.assertEqual(value["prompt"], "--yolo")
349
+ self.assertNotIn("--yolo", value["flags"])
350
+ self.assertTrue(native_mode(["exec", "help"]))
@@ -0,0 +1,150 @@
1
+ """Native argv and working-directory regressions."""
2
+
3
+ import argparse
4
+ from pathlib import Path
5
+ import tempfile
6
+ import unittest
7
+ from unittest.mock import AsyncMock, patch
8
+
9
+ from launch import (
10
+ backend_arguments,
11
+ frontend_arguments,
12
+ native_mode,
13
+ translate,
14
+ working_directory,
15
+ )
16
+ from router import dispatch, resume_target, run_pool
17
+
18
+
19
+ class ArgumentTests(unittest.TestCase):
20
+ def test_values_that_look_like_flags_are_not_reinterpreted(self):
21
+ argv = ["resume", "-c", "--yolo", "-i", "--sandbox", "-m", "--search"]
22
+ self.assertEqual(translate(argv), (argv, {}))
23
+ self.assertEqual(
24
+ backend_arguments(argv), ["-c", "--yolo", "-c", 'model="--search"']
25
+ )
26
+ self.assertFalse(native_mode(["resume", "-i", "--oss"]))
27
+
28
+ def test_attached_options_and_config_boundaries(self):
29
+ argv, overrides = translate(
30
+ [
31
+ "resume",
32
+ "-sworkspace-write",
33
+ "-anever",
34
+ "-mcustom",
35
+ "--",
36
+ "--yolo",
37
+ "--search",
38
+ ]
39
+ )
40
+ self.assertEqual(
41
+ overrides, {"sandbox": "workspace-write", "approvalPolicy": "never"}
42
+ )
43
+ self.assertEqual(backend_arguments(argv), ["-c", 'model="custom"'])
44
+
45
+ def test_approve_for_me_and_private_backend(self):
46
+ argv, overrides = translate(["resume", "--approve-for-me", "--no-daemon"])
47
+ self.assertEqual(argv, ["resume"])
48
+ self.assertEqual(
49
+ overrides,
50
+ {
51
+ "approvalPolicy": "on-request",
52
+ "sandbox": "workspace-write",
53
+ "approvalsReviewer": "auto_review",
54
+ },
55
+ )
56
+
57
+ def test_native_modes_keep_command_and_option_values_distinct(self):
58
+ for argv in (
59
+ ["resume", "--oss"],
60
+ ["resume", "--help"],
61
+ ["--remote=ws://localhost:1", "resume"],
62
+ ):
63
+ with self.subTest(argv=argv):
64
+ self.assertTrue(native_mode(argv))
65
+ for argv in (
66
+ ["resume", "--last"],
67
+ ["exec", "--json", "hello"],
68
+ ["-m", "custom", "review", "--uncommitted"],
69
+ ["-c", "exec", "resume"],
70
+ ["fork", "--", "--oss"],
71
+ ["a prompt", "exec"],
72
+ ):
73
+ with self.subTest(argv=argv):
74
+ self.assertFalse(native_mode(argv))
75
+
76
+ def test_cwd_defaults_and_relative_explicit_options(self):
77
+ with tempfile.TemporaryDirectory() as folder:
78
+ cwd = str(Path(folder).resolve())
79
+ self.assertEqual(frontend_arguments(["resume"], cwd), ["-C", cwd, "resume"])
80
+ for option in (["-C", "child"], ["--cd=child"], ["-Cchild"]):
81
+ argv = ["resume", *option, "--all", "-i", "picture.png"]
82
+ target = str(Path(cwd) / "child")
83
+ self.assertEqual(working_directory(argv, cwd), target)
84
+ self.assertEqual(
85
+ frontend_arguments(argv, cwd),
86
+ ["-C", target, "resume", "--all", "-i", "picture.png"],
87
+ )
88
+ self.assertEqual(
89
+ working_directory(["-c", "--cd=wrong", "--", "-Cwrong"], cwd), cwd
90
+ )
91
+
92
+ def test_uuid_in_an_option_is_not_a_resume_target(self):
93
+ ident = "01a1aa71-5cb7-7bc0-8c23-bcb26f693cf7"
94
+ self.assertIsNone(
95
+ resume_target(["resume", "-m", ident], Path("/missing"), "/tmp")
96
+ )
97
+ self.assertEqual(
98
+ resume_target(["resume", "-m", "custom", ident], Path("/missing"), "/tmp"),
99
+ ident,
100
+ )
101
+ self.assertIsNone(
102
+ resume_target(["resume", "named-session", ident], Path("/missing"), "/tmp")
103
+ )
104
+
105
+
106
+ class LaunchFlowTests(unittest.IsolatedAsyncioTestCase):
107
+ async def test_effective_project_and_frontend_invocation_cwd_are_separate(self):
108
+ with tempfile.TemporaryDirectory() as folder:
109
+ root = Path(folder).resolve()
110
+ project = root / "project"
111
+ project.mkdir()
112
+ state = root / "state"
113
+ state.mkdir()
114
+ args = argparse.Namespace(
115
+ codex_args=["--", "resume", "--cd", "project", "--all", "--yolo"],
116
+ profile=None,
117
+ first=None,
118
+ name=None,
119
+ )
120
+ with (
121
+ patch("os.getcwd", return_value=str(root)),
122
+ patch(
123
+ "settings.load", return_value={"background_on_disconnect": True}
124
+ ) as prefs,
125
+ patch("service.start", return_value="test") as start,
126
+ patch("service.attach", new=AsyncMock(return_value=0)) as attach,
127
+ ):
128
+ self.assertEqual(await run_pool(args, state, "/codex"), 0)
129
+ self.assertEqual(start.call_args.kwargs["cwd"], str(project))
130
+ self.assertEqual(
131
+ start.call_args.kwargs["arguments"],
132
+ ["-C", str(project), "resume", "--all", "--yolo"],
133
+ )
134
+ self.assertEqual(attach.call_args.kwargs["cwd"], str(root))
135
+ self.assertTrue(
136
+ all(call.args[1] == str(project) for call in prefs.call_args_list)
137
+ )
138
+
139
+ async def test_native_utilities_need_no_account_pool_or_protocol_adaptation(self):
140
+ argv = ["--", "mcp", "list", "--", "literal value"]
141
+ args = argparse.Namespace(command="run", codex_args=argv)
142
+ process = type("Process", (), {"wait": AsyncMock(return_value=7)})()
143
+ with (
144
+ patch("launch.native_commands", return_value={"mcp"}),
145
+ patch(
146
+ "asyncio.create_subprocess_exec", new=AsyncMock(return_value=process)
147
+ ) as spawn,
148
+ ):
149
+ self.assertEqual(await dispatch(args, Path("/unused"), "/codex"), 7)
150
+ self.assertEqual(spawn.call_args.args, ("/codex", *argv[1:]))