relay-codex 0.4.1 → 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,233 @@
1
+ """Local launch explanations, redacted diagnostics, saved workspaces, and cleanup."""
2
+
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ import shutil
7
+ import sys
8
+ import time
9
+
10
+ from settings import atomic_json
11
+ from pool import Pool
12
+
13
+
14
+ def explain(arguments, cwd, binary=None):
15
+ from launch import (
16
+ translate,
17
+ working_directory,
18
+ native_mode,
19
+ additional_roots,
20
+ tokens,
21
+ arguments_only,
22
+ )
23
+ from router import VERSION
24
+
25
+ if binary is None:
26
+ from router import codex_path
27
+
28
+ try:
29
+ binary = codex_path()
30
+ except Exception:
31
+ binary = None
32
+ native = native_mode(arguments, binary)
33
+ _, overrides = translate(arguments)
34
+ model = next(
35
+ (
36
+ value
37
+ for name, value, _ in tokens(arguments_only(arguments))
38
+ if name == "--model"
39
+ ),
40
+ None,
41
+ )
42
+ return {
43
+ "relay_version": VERSION,
44
+ "mode": "native" if native else "managed",
45
+ "account_recovery": not native,
46
+ "cwd": working_directory(arguments, cwd),
47
+ "model": model or "Native Codex default",
48
+ "permissions": overrides or "Saved thread or native Codex defaults",
49
+ "additional_directories": additional_roots(
50
+ arguments, working_directory(arguments, cwd)
51
+ ),
52
+ "codex": binary,
53
+ "note": "Native utilities and external/local-provider endpoints retain native behavior."
54
+ if native
55
+ else "Relay manages account resets, temporary failures, and the saved conversation.",
56
+ }
57
+
58
+
59
+ def diagnostics(state, output):
60
+ """Allowlisted operational facts only; never read credentials or raw job arguments."""
61
+ from router import VERSION
62
+ import platform
63
+
64
+ pool = Pool(state)
65
+ try:
66
+ details = pool.detail()
67
+ with pool.db() as db:
68
+ health = db.execute("PRAGMA quick_check").fetchone()[0]
69
+ events = [
70
+ r[0]
71
+ for r in db.execute(
72
+ "SELECT kind FROM events ORDER BY id DESC LIMIT 100"
73
+ )
74
+ ]
75
+ value = {
76
+ "relay_version": VERSION,
77
+ "python": platform.python_version(),
78
+ "platform": sys.platform,
79
+ "database": "ok" if health == "ok" else "failed",
80
+ "active_sessions": len(details),
81
+ "session_states": [r.get("state", "unknown") for r in details],
82
+ "recent_event_types": events,
83
+ }
84
+ destination = Path(output).expanduser()
85
+ with destination.open("x", encoding="utf-8") as stream:
86
+ os.chmod(destination, 0o600)
87
+ json.dump(value, stream, indent=2)
88
+ stream.write("\n")
89
+ return str(destination)
90
+ finally:
91
+ pool.close()
92
+
93
+
94
+ def workspace(state, action, name=None, sessions=None, binary=None):
95
+ import service
96
+
97
+ path = state / "workspaces.json"
98
+ data = json.loads(path.read_text()) if path.exists() else {}
99
+ pool = Pool(state)
100
+ try:
101
+ if action == "list":
102
+ return {key: len(value) for key, value in data.items()}
103
+ if action == "remove":
104
+ data.pop(name, None)
105
+ atomic_json(path, data)
106
+ return {"removed": name}
107
+ if action == "save":
108
+ live = pool.detail()
109
+ chosen = [
110
+ r
111
+ for r in live
112
+ if not sessions or any(r["id"].startswith(s) for s in sessions)
113
+ ]
114
+ if not chosen or sessions and len(chosen) != len(sessions):
115
+ raise ValueError(
116
+ "Choose live session IDs from relay status, or omit IDs to save every live session"
117
+ )
118
+ jobs = []
119
+ for row in chosen:
120
+ job_path = service.folder(state) / (row["id"] + ".job.json")
121
+ if not job_path.exists():
122
+ continue
123
+ jobs.append(row["id"])
124
+ if not jobs:
125
+ raise ValueError("No supervised sessions were available to save")
126
+ data[name] = jobs
127
+ atomic_json(path, data)
128
+ return {"saved": name, "sessions": len(jobs)}
129
+ if name not in data:
130
+ raise ValueError("Unknown workspace")
131
+ alive = {r["id"] for r in pool.detail()}
132
+ result = []
133
+ for ident in data[name]:
134
+ if ident not in alive:
135
+ service.restart(state, ident)
136
+ result.append({"session": ident, "attach": "relay attach " + ident})
137
+ return result
138
+ finally:
139
+ pool.close()
140
+
141
+
142
+ def cleanup(state, *, apply=False, days=30, jobs=False):
143
+ """Default to a dry run. Preserve runtimes used by live or restartable jobs."""
144
+ import runtime
145
+
146
+ pool = Pool(state)
147
+ try:
148
+ live = {r["id"] for r in pool.detail()}
149
+ finally:
150
+ pool.close()
151
+ threshold = time.time() - days * 86400
152
+ protected = {str(Path(__file__).parent.resolve())}
153
+ workspaces_file = state / "workspaces.json"
154
+ saved_workspaces = (
155
+ json.loads(workspaces_file.read_text()) if workspaces_file.exists() else {}
156
+ )
157
+ workspace_jobs = {ident for group in saved_workspaces.values() for ident in group}
158
+ value = runtime.index(state)
159
+ native_protected = {
160
+ row["sha256"][:20] for row in value["runtimes"][-1:] if row.get("sha256")
161
+ }
162
+ if value.get("selected"):
163
+ native_protected.add(value["selected"][:20])
164
+ candidates = []
165
+ protect_all_python = False
166
+ for path in (state / "services").glob("*.job.json"):
167
+ try:
168
+ job = json.loads(path.read_text())
169
+ except (OSError, ValueError):
170
+ protect_all_python = True
171
+ continue
172
+ ident = path.name.removesuffix(".job.json")
173
+ old_stopped = (
174
+ ident not in live
175
+ and job.get("state") in {"stopped", "removed"}
176
+ and path.stat().st_mtime < threshold
177
+ )
178
+ if old_stopped and jobs and ident not in workspace_jobs:
179
+ candidates.extend(
180
+ p
181
+ for p in path.parent.glob(ident + ".*")
182
+ if p.is_file() and not p.is_symlink()
183
+ )
184
+ continue
185
+ if job.get("binary"):
186
+ protected.add(str(Path(job["binary"]).resolve()))
187
+ if job.get("relay_runtime"):
188
+ protected.add(str(Path(job["relay_runtime"]).resolve()))
189
+ elif ident in live:
190
+ protect_all_python = True
191
+ for folder in ["relay-runtimes", "native-runtimes"]:
192
+ root = state / folder
193
+ if not root.exists() or folder == "relay-runtimes" and protect_all_python:
194
+ continue
195
+ for path in root.iterdir():
196
+ resolved = str(path.resolve())
197
+ if (
198
+ path.is_symlink()
199
+ or not path.is_dir()
200
+ or path.stat().st_mtime >= threshold
201
+ ):
202
+ continue
203
+ if folder == "native-runtimes" and path.name in native_protected:
204
+ continue
205
+ if any(p == resolved or p.startswith(resolved + os.sep) for p in protected):
206
+ continue
207
+ candidates.append(path)
208
+ # Recheck live references immediately before mutations. Never delete from another user's state.
209
+ if apply:
210
+ guard = Pool(state)
211
+ try:
212
+ if {r["id"] for r in guard.detail()} != live:
213
+ raise ValueError("Sessions changed during cleanup; rerun the dry run")
214
+ finally:
215
+ guard.close()
216
+ for path in candidates:
217
+ if path.is_symlink():
218
+ raise ValueError(
219
+ "Cleanup encountered a changed symlink; nothing further was removed"
220
+ )
221
+ if path.is_dir():
222
+ shutil.rmtree(path)
223
+ else:
224
+ path.unlink(missing_ok=True)
225
+ value["runtimes"] = [
226
+ r for r in value["runtimes"] if Path(r["binary"]).is_file()
227
+ ]
228
+ atomic_json(state / "runtimes.json", value)
229
+ return {
230
+ "dry_run": not apply,
231
+ "paths": [str(p) for p in candidates],
232
+ "count": len(candidates),
233
+ }
@@ -0,0 +1,74 @@
1
+ """Wait for local filesystem changes; never fetch allowance or run inference."""
2
+
3
+ import asyncio
4
+ import json
5
+ import os
6
+ from pathlib import Path
7
+ import select
8
+
9
+
10
+ def fingerprint(value):
11
+ """Heartbeats and sample time do not represent a dashboard change."""
12
+ value = {**value, "at": None}
13
+ value["sessions"] = [
14
+ {k: v for k, v in item.items() if k != "seen"}
15
+ for item in value.get("sessions", [])
16
+ ]
17
+ return json.dumps(value, sort_keys=True)
18
+
19
+
20
+ class LocalChanges:
21
+ def __init__(self, state):
22
+ self.state = Path(state)
23
+ self.queue = select.kqueue() if hasattr(select, "kqueue") else None
24
+ self.files = []
25
+ self.signature = None
26
+
27
+ def close(self):
28
+ if self.queue:
29
+ self.queue.close()
30
+ for fd in self.files:
31
+ os.close(fd)
32
+ self.files.clear()
33
+
34
+ def wait(self, timeout=30):
35
+ paths = [
36
+ self.state,
37
+ self.state / "services",
38
+ self.state / "accounts",
39
+ self.state / "pool.sqlite",
40
+ self.state / "pool.sqlite-wal",
41
+ self.state / "settings.json",
42
+ ]
43
+ if not self.queue:
44
+ import time
45
+
46
+ time.sleep(timeout)
47
+ return
48
+ for fd in self.files:
49
+ os.close(fd)
50
+ self.files = []
51
+ registrations = []
52
+ for path in paths:
53
+ try:
54
+ fd = os.open(path, os.O_RDONLY)
55
+ except FileNotFoundError:
56
+ continue
57
+ self.files.append(fd)
58
+ registrations.append(
59
+ select.kevent(
60
+ fd,
61
+ filter=select.KQ_FILTER_VNODE,
62
+ flags=select.KQ_EV_ADD | select.KQ_EV_CLEAR,
63
+ fflags=select.KQ_NOTE_WRITE
64
+ | select.KQ_NOTE_RENAME
65
+ | select.KQ_NOTE_DELETE
66
+ | select.KQ_NOTE_ATTRIB,
67
+ )
68
+ )
69
+ self.queue.control(registrations, max(1, len(registrations)), timeout)
70
+
71
+ async def changed(self, timeout=30):
72
+ await asyncio.to_thread(self.wait, timeout)
73
+ # Coalesce a burst of updates into one consistent snapshot.
74
+ await asyncio.sleep(0.15)
@@ -177,7 +177,7 @@ for raw in sys.stdin:
177
177
  }
178
178
  elif method == "thread/loaded/list":
179
179
  result = {"data": change(lambda d: list(d["threads"]))}
180
- elif method == "turn/start":
180
+ elif method in {"turn/start", "review/start"}:
181
181
  thread = params["threadId"]
182
182
  turn = str(uuid.uuid4())
183
183
  value = {"id": turn, "status": "inProgress", "items": [], "error": None}
@@ -189,7 +189,9 @@ for raw in sys.stdin:
189
189
  "activeFlags": [],
190
190
  }
191
191
  data["threads"][thread]["runtime"] = os.getpid()
192
- return sum(c["method"] == "turn/start" for c in data["calls"])
192
+ return sum(
193
+ c["method"] in {"turn/start", "review/start"} for c in data["calls"]
194
+ )
193
195
 
194
196
  count = change(begin)
195
197
  fault = (
@@ -0,0 +1,320 @@
1
+ """Regression cases reproduced by the audit. No real accounts or model calls."""
2
+
3
+ import asyncio
4
+ import contextlib
5
+ import io
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ import tempfile
10
+ import unittest
11
+ from unittest.mock import patch
12
+
13
+ from router import Controller, RemoteError, parse_arguments, resolve_resume_target
14
+ from test_router import FakeAccount, FakeBackend, FakeClient, completed
15
+ from test_pool import Clock
16
+ from pool import Pool, process_birth
17
+ from settings import atomic_json
18
+ import commands
19
+ import service
20
+
21
+
22
+ class RecoveryRegressions(unittest.IsolatedAsyncioTestCase):
23
+ async def test_late_failure_cannot_bypass_review_hold(self):
24
+ with tempfile.TemporaryDirectory() as folder:
25
+ clock = Clock()
26
+ pool = Pool(Path(folder), clock=clock)
27
+ backend = FakeBackend()
28
+ c = Controller(
29
+ [FakeAccount("one")],
30
+ backend,
31
+ pool=pool,
32
+ clock=clock,
33
+ sleep=clock.sleep,
34
+ report=lambda _: None,
35
+ )
36
+ c.client = FakeClient()
37
+ c.last_turn["thread-1"] = {"threadId": "thread-1"}
38
+ c.hold_for_review("thread-1")
39
+ await c.event(completed(code="policyCapacityUnavailable"))
40
+ await c.recovery
41
+ self.assertFalse(any(method == "turn/start" for method, _ in backend.calls))
42
+ self.assertTrue(pool.intent("thread-1")["review_required"])
43
+ await c.control("retry", {"thread": "thread-1"})
44
+ await c.recovery
45
+ self.assertTrue(any(method == "turn/start" for method, _ in backend.calls))
46
+ pool.close()
47
+
48
+ async def test_picker_permission_control_applies_before_thread_selection(self):
49
+ c = Controller([FakeAccount("one")], FakeBackend(), report=lambda _: None)
50
+ c.launch_overrides = {
51
+ "sandbox": "danger-full-access",
52
+ "approvalPolicy": "never",
53
+ }
54
+ await c.control(
55
+ "permissions", {"sandbox": "read-only", "approvalPolicy": "on-request"}
56
+ )
57
+ self.assertEqual(
58
+ c.launch_overrides, {"sandbox": "read-only", "approvalPolicy": "on-request"}
59
+ )
60
+ c.client = FakeClient()
61
+ await c.forward({"id": 1, "method": "thread/start", "params": {}}, {})
62
+ self.assertEqual(c.backend.calls[-1][1]["sandbox"], "read-only")
63
+
64
+ async def test_more_than_one_thousand_overloads_do_not_grow_call_stack(self):
65
+ clock = Clock()
66
+ backend = FakeBackend()
67
+ rejections = 1100
68
+ original = backend.call
69
+
70
+ async def overloaded(method, params=None, **kw):
71
+ nonlocal rejections
72
+ if method == "turn/start" and rejections:
73
+ rejections -= 1
74
+ raise RemoteError({"code": -32001})
75
+ return await original(method, params, **kw)
76
+
77
+ backend.call = overloaded
78
+ c = Controller(
79
+ [FakeAccount("one"), FakeAccount("two")],
80
+ backend,
81
+ clock=clock,
82
+ sleep=clock.sleep,
83
+ report=lambda _: None,
84
+ )
85
+ c.client = FakeClient()
86
+ c.last_turn["thread-1"] = {"threadId": "thread-1"}
87
+ await c.recover("thread-1", 0, "transient")
88
+ self.assertEqual(rejections, 0)
89
+ self.assertTrue(any(method == "turn/start" for method, _ in backend.calls))
90
+ self.assertFalse(c.paused_uncertain)
91
+
92
+ async def test_resume_lookup_uses_native_filtering_and_names(self):
93
+ class Backend:
94
+ def __init__(self):
95
+ self.calls = []
96
+
97
+ async def call(self, method, params):
98
+ self.calls.append((method, dict(params)))
99
+ return {
100
+ "data": [{"id": "interactive", "name": "named", "cwd": "/project"}]
101
+ }
102
+
103
+ backend = Backend()
104
+ selected = await resolve_resume_target(
105
+ ["resume", "--last"], Path("/home"), "/project", backend=backend
106
+ )
107
+ self.assertEqual(selected, "interactive")
108
+ self.assertNotIn("sourceKinds", backend.calls[-1][1])
109
+ self.assertEqual(backend.calls[-1][1]["cwd"], "/project")
110
+ selected = await resolve_resume_target(
111
+ ["resume", "named", "--all", "--include-non-interactive"],
112
+ Path("/home"),
113
+ "/project",
114
+ backend=backend,
115
+ )
116
+ self.assertEqual(selected, "interactive")
117
+ self.assertIn("exec", backend.calls[-1][1]["sourceKinds"])
118
+ self.assertNotIn("cwd", backend.calls[-1][1])
119
+
120
+ async def test_watch_ignores_heartbeats_and_redraws_only_meaningful_changes(self):
121
+ args = type(
122
+ "Args", (), {"watch": True, "json": True, "all": False, "interval": 30}
123
+ )()
124
+ count = 0
125
+ values = [
126
+ {"at": 1, "sessions": [{"id": "a", "seen": 1, "state": "idle"}]},
127
+ {"at": 2, "sessions": [{"id": "a", "seen": 2, "state": "idle"}]},
128
+ {"at": 3, "sessions": [{"id": "a", "seen": 3, "state": "working"}]},
129
+ ]
130
+
131
+ class Watch:
132
+ def __init__(self, state):
133
+ pass
134
+
135
+ async def changed(self, timeout):
136
+ nonlocal count
137
+ count += 1
138
+ if count == 3:
139
+ raise asyncio.CancelledError()
140
+
141
+ def close(self):
142
+ pass
143
+
144
+ output = io.StringIO()
145
+ with (
146
+ patch("watch.LocalChanges", Watch),
147
+ patch("commands.snapshot", side_effect=values),
148
+ contextlib.redirect_stdout(output),
149
+ ):
150
+ with self.assertRaises(asyncio.CancelledError):
151
+ await commands.status(args, Path("/unused"))
152
+ self.assertEqual(len(output.getvalue().splitlines()), 2)
153
+
154
+ async def test_control_wait_reports_rejection(self):
155
+ with tempfile.TemporaryDirectory() as folder:
156
+ state = Path(folder)
157
+ pool = Pool(state)
158
+ pool.session = "worker"
159
+ pool.assign("one")
160
+ args = parse_arguments(["pause", "worker", "--wait"])
161
+
162
+ async def reject(pool, ident, timeout):
163
+ raise ValueError("rejected")
164
+
165
+ with patch("service.wait_control", reject):
166
+ with self.assertRaisesRegex(ValueError, "rejected"):
167
+ await commands.control(args, state)
168
+ pool.close()
169
+
170
+
171
+ class WorkerRegressions(unittest.IsolatedAsyncioTestCase):
172
+ async def asyncSetUp(self):
173
+ from test_services import ServiceTests
174
+
175
+ self.case = ServiceTests("test_background_work_completes_without_frontend")
176
+ await self.case.asyncSetUp()
177
+
178
+ async def asyncTearDown(self):
179
+ await self.case.asyncTearDown()
180
+
181
+ async def test_worker_restores_reviewer_and_workspace_roots(self):
182
+ from test_services import wait_for
183
+ from router import RPC
184
+
185
+ c = self.case
186
+ await wait_for(lambda: json.loads(c.job.read_text()).get("resume"))
187
+ await wait_for(
188
+ lambda: any(
189
+ t["turns"] and t["turns"][-1]["status"] == "completed"
190
+ for t in json.loads((c.home / "fake-history.json").read_text())[
191
+ "threads"
192
+ ].values()
193
+ )
194
+ )
195
+ thread = json.loads(c.job.read_text())["resume"]
196
+ c.task.cancel()
197
+ await asyncio.gather(c.task, return_exceptions=True)
198
+ pool = Pool(c.state)
199
+ pool.session = c.ident
200
+ pool.intent(
201
+ thread,
202
+ {
203
+ "role": "foreground",
204
+ "accepted": True,
205
+ "state": "completed",
206
+ "settings": {
207
+ "approvalsReviewer": "auto_review",
208
+ "runtimeWorkspaceRoots": [str(c.home), "/private/tmp/audit-extra"],
209
+ },
210
+ },
211
+ )
212
+ observed = []
213
+ original = RPC.call
214
+
215
+ async def capture(rpc, method, params=None, **kw):
216
+ if method == "thread/resume":
217
+ observed.append(params)
218
+ return await original(rpc, method, params, **kw)
219
+
220
+ with patch.object(RPC, "call", capture):
221
+ c.task = asyncio.create_task(service.serve(c.state, c.ident, c.binary))
222
+ await wait_for(c.meta.exists)
223
+ self.assertEqual(observed[0]["approvalsReviewer"], "auto_review")
224
+ self.assertIn("/private/tmp/audit-extra", observed[0]["runtimeWorkspaceRoots"])
225
+ pool.close()
226
+
227
+ async def test_project_selection_updates_restart_and_attach_metadata(self):
228
+ from websockets.asyncio.client import connect
229
+
230
+ c = self.case
231
+ other = c.home / "other"
232
+ other.mkdir()
233
+ value = json.loads(c.meta.read_text())
234
+ async with connect(
235
+ value["url"],
236
+ additional_headers={"Authorization": "Bearer " + value["token"]},
237
+ proxy=None,
238
+ ) as ws:
239
+ await ws.send(
240
+ json.dumps(
241
+ {
242
+ "id": "select",
243
+ "method": "thread/start",
244
+ "params": {"cwd": str(other)},
245
+ }
246
+ )
247
+ )
248
+ while json.loads(await ws.recv()).get("id") != "select":
249
+ pass
250
+ self.assertEqual(json.loads(c.meta.read_text())["cwd"], str(other))
251
+ self.assertEqual(json.loads(c.job.read_text())["cwd"], str(other))
252
+
253
+
254
+ class AttachmentRegressions(unittest.IsolatedAsyncioTestCase):
255
+ async def test_new_worker_reconnects_without_replaying_original_prompt(self):
256
+ with tempfile.TemporaryDirectory() as folder:
257
+ state = Path(folder)
258
+ root = service.folder(state)
259
+ job = root / "task.job.json"
260
+ meta = root / "task.connection.json"
261
+ atomic_json(job, {"binary": "/codex", "state": "running"})
262
+ old = {
263
+ "pid": os.getpid(),
264
+ "birth": process_birth(os.getpid()),
265
+ "url": "ws://127.0.0.1:1",
266
+ "token": "FAKE",
267
+ "home": folder,
268
+ "cwd": folder,
269
+ "thread": "saved",
270
+ }
271
+ atomic_json(meta, old)
272
+
273
+ class Process:
274
+ def __init__(self, done=False):
275
+ self.returncode = 0 if done else None
276
+ self.finished = asyncio.Event()
277
+
278
+ async def wait(self):
279
+ if self.returncode is None:
280
+ await self.finished.wait()
281
+ return self.returncode
282
+
283
+ def terminate(self):
284
+ self.returncode = 0
285
+ self.finished.set()
286
+
287
+ def kill(self):
288
+ self.terminate()
289
+
290
+ spawned = []
291
+
292
+ async def spawn(*argv, **kwargs):
293
+ spawned.append(argv)
294
+ if len(spawned) == 1:
295
+ atomic_json(
296
+ meta,
297
+ {
298
+ **old,
299
+ "pid": os.getppid(),
300
+ "birth": process_birth(os.getppid()),
301
+ "url": "ws://127.0.0.1:2",
302
+ "token": "NEW_FAKE",
303
+ },
304
+ )
305
+ return Process(done=len(spawned) > 1)
306
+
307
+ with patch("asyncio.create_subprocess_exec", spawn):
308
+ self.assertEqual(
309
+ await service.attach(state, "task", "/codex", ["original prompt"]),
310
+ 0,
311
+ )
312
+ self.assertEqual(len(spawned), 2)
313
+ self.assertIn("saved", spawned[1])
314
+ self.assertNotIn("original prompt", spawned[1])
315
+
316
+ def test_takeover_can_follow_session_but_not_native_separator(self):
317
+ self.assertTrue(parse_arguments(["attach", "task", "--takeover"]).takeover)
318
+ self.assertFalse(
319
+ parse_arguments(["attach", "task", "--", "--takeover"]).takeover
320
+ )
@@ -33,12 +33,10 @@ class LaunchTests(unittest.TestCase):
33
33
  self.assertEqual(args, ["resume", "--", "--yolo"])
34
34
  self.assertEqual(value, {})
35
35
 
36
- def test_config_profile_forwarded_to_backend(self):
36
+ def test_config_forwarded_without_native_only_profile_flag(self):
37
37
  self.assertEqual(
38
- backend_arguments(
39
- ["resume", "-p", "work", "-c", 'model_reasoning_effort="high"']
40
- ),
41
- ["-p", "work", "-c", 'model_reasoning_effort="high"'],
38
+ backend_arguments(["resume", "-c", 'model_reasoning_effort="high"']),
39
+ ["-c", 'model_reasoning_effort="high"'],
42
40
  )
43
41
 
44
42
  def test_setup_supports_one_or_arbitrary_count(self):