cdx-manager 0.25.1 → 0.25.3

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.
@@ -1068,13 +1068,15 @@ def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_pr
1068
1068
  )
1069
1069
 
1070
1070
 
1071
- def _headless_artifact_paths(session, run_id=None):
1071
+ def _headless_artifact_paths(session, run_id=None, attempt=1):
1072
1072
  run_id = run_id or uuid.uuid4().hex
1073
1073
  log_dir = _get_launch_transcript_dir(session)
1074
1074
  os.makedirs(log_dir, exist_ok=True)
1075
- prefix = os.path.join(log_dir, f"cdx-run-{run_id}")
1075
+ attempt_id = f"{run_id}:{attempt}"
1076
+ prefix = os.path.join(log_dir, f"cdx-run-{run_id}-attempt-{attempt}")
1076
1077
  return {
1077
1078
  "run_id": run_id,
1079
+ "attempt_id": attempt_id,
1078
1080
  "transcript_path": os.path.abspath(f"{prefix}.log"),
1079
1081
  "stdout_path": os.path.abspath(f"{prefix}.stdout.log"),
1080
1082
  "stderr_path": os.path.abspath(f"{prefix}.stderr.log"),
@@ -1205,7 +1207,33 @@ def _read_permission_denials(stdout_path):
1205
1207
 
1206
1208
 
1207
1209
  def _run_headless_provider_command(session, cwd=None, env_override=None, initial_prompt=None,
1208
- timeout_seconds=None, spawn=None, run_id=None, budget_check=None):
1210
+ timeout_seconds=None, spawn=None, run_id=None, budget_check=None, attempt=1):
1211
+ from .remote_run import remote_auth_lock, temporary_auth
1212
+ from .workspace_sandbox import private_auth_home
1213
+
1214
+ isolated_codex = session.get("provider") == PROVIDER_CODEX and (session.get("launch") or {}).get("isolate_workspace")
1215
+ if isolated_codex:
1216
+ handlers = _route_termination_to_interrupt()
1217
+ try:
1218
+ with remote_auth_lock(_get_auth_home(session)):
1219
+ auth = temporary_auth(_get_auth_home(session))
1220
+ with private_auth_home(auth) as directory:
1221
+ return _run_headless_provider_attempt(session, cwd, env_override, initial_prompt,
1222
+ timeout_seconds, spawn, run_id, budget_check, directory, attempt)
1223
+ except KeyboardInterrupt as cause:
1224
+ error = CdxError('Isolated Codex run was cancelled.', 130)
1225
+ error.cancelled = True
1226
+ raise error from cause
1227
+ except (OSError, RuntimeError) as error:
1228
+ raise CdxError(str(error)) from error
1229
+ finally:
1230
+ _restore_signal_handlers(handlers)
1231
+ return _run_headless_provider_attempt(session, cwd, env_override, initial_prompt,
1232
+ timeout_seconds, spawn, run_id, budget_check, attempt=attempt)
1233
+
1234
+
1235
+ def _run_headless_provider_attempt(session, cwd=None, env_override=None, initial_prompt=None,
1236
+ timeout_seconds=None, spawn=None, run_id=None, budget_check=None, observation_home=None, attempt=1):
1209
1237
  spawn = spawn or subprocess.Popen
1210
1238
  spec = _build_headless_launch_spec(
1211
1239
  session,
@@ -1248,14 +1276,23 @@ def _run_headless_provider_command(session, cwd=None, env_override=None, initial
1248
1276
  [spec["command"], *spec["args"]], Path(cwd),
1249
1277
  network=launch.get("network_access", False),
1250
1278
  writable=launch.get("permission") != "review",
1251
- source=spec["options"]["env"], auth_home=_get_auth_home(session),
1252
- provider=session.get("provider"))
1279
+ source=spec["options"]["env"], auth_home=observation_home or _get_auth_home(session),
1280
+ provider=session.get("provider"),
1281
+ **({"observation_home": os.path.join(observation_home, "sessions")} if observation_home else {}))
1253
1282
  except (OSError, RuntimeError) as error:
1254
1283
  raise CdxError(str(error)) from error
1255
1284
  spec.update(command=argv[0], args=argv[1:])
1256
1285
  spec["options"]["env"] = child_env
1257
- paths = _headless_artifact_paths(session, run_id=run_id)
1286
+ paths = _headless_artifact_paths(session, run_id=run_id, attempt=attempt)
1258
1287
  start_time = datetime.now(timezone.utc)
1288
+
1289
+ def snapshot():
1290
+ if observation_home:
1291
+ from .run_observation import codex_attempt_evidence
1292
+ paths["usage_evidence"] = codex_attempt_evidence(
1293
+ observation_home, paths["stdout_path"], start_time.isoformat())
1294
+ paths["usage_evidence"].update(run_id=paths["run_id"], attempt_id=paths.get("attempt_id"))
1295
+
1259
1296
  command = spec["command"]
1260
1297
  if spawn is subprocess.Popen:
1261
1298
  command = _resolve_command(command, spec.get("options", {}).get("env"))
@@ -1264,6 +1301,12 @@ def _run_headless_provider_command(session, cwd=None, env_override=None, initial
1264
1301
  timed_out = False
1265
1302
  cancelled = False
1266
1303
  budget_exceeded = False
1304
+ credential_expired = False
1305
+ expires_at = None
1306
+ if observation_home:
1307
+ from .remote_run import token_expiry
1308
+ with open(os.path.join(observation_home, 'auth.json')) as handle:
1309
+ expires_at = token_expiry(json.load(handle)['tokens']['access_token'])
1267
1310
  with open(paths["stdout_path"], "w", encoding="utf-8", errors="replace") as stdout_file, \
1268
1311
  open(paths["stderr_path"], "w", encoding="utf-8", errors="replace") as stderr_file:
1269
1312
  options = {k: v for k, v in spec.get("options", {}).items() if k not in ("stdio", "stdout", "stderr")}
@@ -1292,10 +1335,15 @@ def _run_headless_provider_command(session, cwd=None, env_override=None, initial
1292
1335
  # this supervisor and keep its run "running" forever.
1293
1336
  previous_handlers = _route_termination_to_interrupt()
1294
1337
  try:
1295
- if budget_check is not None:
1338
+ if budget_check is not None or expires_at is not None:
1296
1339
  deadline = time.monotonic() + timeout_seconds if timeout_seconds is not None else None
1297
1340
  while True:
1298
- if budget_check(paths):
1341
+ snapshot()
1342
+ if expires_at is not None and time.time() >= expires_at:
1343
+ credential_expired = True
1344
+ _terminate_child_tree(child)
1345
+ break
1346
+ if budget_check and budget_check(paths):
1299
1347
  budget_exceeded = True
1300
1348
  _terminate_child_tree(child)
1301
1349
  break
@@ -1304,7 +1352,8 @@ def _run_headless_provider_command(session, cwd=None, env_override=None, initial
1304
1352
  raise subprocess.TimeoutExpired(command, timeout_seconds)
1305
1353
  try:
1306
1354
  child.wait(timeout=min(0.25, remaining) if remaining is not None else 0.25)
1307
- budget_exceeded = budget_check(paths)
1355
+ snapshot()
1356
+ budget_exceeded = bool(budget_check and budget_check(paths))
1308
1357
  break
1309
1358
  except subprocess.TimeoutExpired:
1310
1359
  continue
@@ -1325,8 +1374,14 @@ def _run_headless_provider_command(session, cwd=None, env_override=None, initial
1325
1374
  cancelled = True
1326
1375
  _terminate_child_tree(child)
1327
1376
  finally:
1377
+ snapshot()
1328
1378
  _restore_signal_handlers(previous_handlers)
1329
1379
 
1380
+ if credential_expired or (expires_at is not None and time.time() >= expires_at):
1381
+ _combine_headless_transcript(paths)
1382
+ error = CdxError('Isolated temporary credential expired; start a new run from the controller.', code='remote_credential_expired')
1383
+ error.run_info = _headless_run_info(paths, spec, start_time, child.returncode)
1384
+ raise error
1330
1385
  if cancelled:
1331
1386
  _combine_headless_transcript(paths)
1332
1387
  returncode = getattr(child, "returncode", None)
@@ -0,0 +1,230 @@
1
+ """Controller-side temporary Codex credentials and SSH transport."""
2
+
3
+ import base64
4
+ import json
5
+ import math
6
+ import os
7
+ import pathlib
8
+ import re
9
+ import subprocess
10
+ import time
11
+ from contextlib import contextmanager
12
+ from datetime import datetime, timezone
13
+
14
+ from .codex_usage import _request_codex_app_server, codex_auth_lock
15
+ from .errors import CdxError
16
+
17
+
18
+ def token_expiry(token):
19
+ try:
20
+ part = token.split('.')[1]
21
+ expiry = float(json.loads(base64.urlsafe_b64decode(part + '=' * (-len(part) % 4)))['exp'])
22
+ if not math.isfinite(expiry) or expiry <= 0:
23
+ raise ValueError('Invalid expiration')
24
+ return expiry
25
+ except (ValueError, KeyError, IndexError, TypeError) as error:
26
+ raise CdxError('Cannot determine remote access token expiration; log in on the controller.') from error
27
+
28
+
29
+ def needs_refresh(auth, now):
30
+ try:
31
+ refreshed = datetime.fromisoformat(auth['last_refresh'].replace('Z', '+00:00')).timestamp()
32
+ except (ValueError, KeyError, TypeError):
33
+ return True
34
+ return now - refreshed > 7 * 86400 or token_expiry(auth['tokens']['access_token']) - now < 86400
35
+
36
+
37
+ def refresh_auth(home):
38
+ result = _request_codex_app_server(home, 'account/read', {'refreshToken': True}, 'refresh_failed', timeout=30)
39
+ if not result.get('ok'):
40
+ raise CdxError('Controller Codex credential refresh failed; log in on the controller.')
41
+
42
+
43
+ def temporary_auth(home, now=None):
44
+ now = time.time() if now is None else now
45
+ try:
46
+ auth = json.loads((pathlib.Path(home) / 'auth.json').read_text())
47
+ if needs_refresh(auth, now):
48
+ refresh_auth(home)
49
+ auth = json.loads((pathlib.Path(home) / 'auth.json').read_text())
50
+ if needs_refresh(auth, now):
51
+ raise CdxError('Controller refresh did not produce a fresh remote credential.')
52
+ tokens = {key: auth['tokens'][key] for key in ('access_token', 'id_token', 'account_id')}
53
+ tokens['refresh_token'] = ''
54
+ result = {'tokens': tokens, 'last_refresh': auth['last_refresh']}
55
+ rotating = auth['tokens'].get('refresh_token')
56
+ if result['tokens']['refresh_token'] != '' or (rotating and rotating in json.dumps(result)):
57
+ raise CdxError('Refusing to send a refresh token to a worker.')
58
+ return result
59
+ except (OSError, ValueError, KeyError, TypeError) as error:
60
+ raise CdxError('Cannot read controller Codex OAuth credentials; log in on the controller.') from error
61
+
62
+
63
+ def nonsecret_config(home):
64
+ # Copy only known scalar preferences. Provider URLs, hooks, env and arbitrary
65
+ # config entries can contain secrets or refer to controller-only programs.
66
+ path = pathlib.Path(home) / 'config.toml'
67
+ if not path.exists():
68
+ return ''
69
+ allowed = {'model', 'model_reasoning_effort', 'service_tier', 'personality',
70
+ 'model_context_window', 'model_auto_compact_token_limit'}
71
+ lines = []
72
+ for line in path.read_text().splitlines():
73
+ if line.lstrip().startswith('['):
74
+ break
75
+ key, sep, value = line.partition('=')
76
+ if sep and key.strip() in allowed and re.fullmatch(r'\s*("[a-zA-Z0-9_. /-]+"|[0-9]+)\s*', value):
77
+ lines.append(line)
78
+ return '\n'.join(lines) + '\n'
79
+
80
+
81
+ def worker_input():
82
+ # One JSON line frames the source, leaving the next line for the payload.
83
+ directory = pathlib.Path(__file__).parent
84
+ source = 'SANDBOX_SOURCE = ' + repr((directory / 'workspace_sandbox.py').read_text()) + '\n'
85
+ source += (directory / 'remote_worker.py').read_text()
86
+ return json.dumps(source).encode() + b'\n'
87
+
88
+
89
+ def ssh_command(target, mode, remote_prefix=None):
90
+ if not re.fullmatch(r'[a-zA-Z0-9_@.:[\]-]+', target) or target.startswith('-'):
91
+ raise CdxError('Invalid --remote SSH target; use a host alias or user@host.')
92
+ prefix = (remote_prefix or '').strip()
93
+ if prefix and not re.fullmatch(r'[a-zA-Z0-9_./:=+ -]+', prefix):
94
+ raise CdxError('Invalid --remote-prefix; use a plain command prefix such as wsl.exe -d Ubuntu --.')
95
+ if mode not in ('probe', 'run'):
96
+ raise CdxError('Invalid remote worker mode.')
97
+ # Double quotes work in both POSIX sh and cmd.exe. The bootstrap contains
98
+ # no embedded double quotes, shell expansions, or worker source in argv.
99
+ command = (prefix + ' ' if prefix else '') + 'python3 -u -c "import sys,json;exec(json.loads(sys.stdin.buffer.readline()))" ' + mode
100
+ if len(command) > 8000:
101
+ raise CdxError('Remote command prefix exceeds the Windows command-line limit.')
102
+ return ['ssh', '-T', '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=5', '-o', 'ConnectionAttempts=1',
103
+ '-o', 'ServerAliveInterval=5', '-o', 'ServerAliveCountMax=1', target, command]
104
+
105
+
106
+ def preflight(target, env=None, *, remote_prefix=None, isolate_workspace=False, cwd=None, network=False, writable=True):
107
+ payload = {'isolate_workspace': isolate_workspace, 'cwd': cwd, 'network': network, 'writable': writable}
108
+ try:
109
+ local = subprocess.check_output(['codex', '--version'], env=env, stderr=subprocess.DEVNULL, timeout=10).decode().strip()
110
+ result = subprocess.run(ssh_command(target, 'probe', remote_prefix), env=env,
111
+ input=worker_input() + json.dumps(payload).encode() + b'\n',
112
+ capture_output=True, timeout=30)
113
+ except (OSError, subprocess.TimeoutExpired) as error:
114
+ raise CdxError('Remote worker unreachable or Codex CLI unavailable.', code='remote_unavailable') from error
115
+ if result.returncode:
116
+ message = ('Remote workspace isolation unavailable; requires bubblewrap, pasta, nftables, working namespaces and a valid checkout.'
117
+ if isolate_workspace else 'Remote worker unreachable or missing Python 3, Codex or memory-backed /dev/shm.')
118
+ raise CdxError(message, code='remote_unavailable')
119
+ if local != result.stdout.decode().strip():
120
+ raise CdxError('Remote Codex CLI version mismatch; install the controller version on the worker.', code='remote_version_mismatch')
121
+ return local
122
+
123
+
124
+ @contextmanager
125
+ def remote_auth_lock(home):
126
+ try:
127
+ with codex_auth_lock(home, strict=True) as acquired:
128
+ if not acquired:
129
+ raise CdxError('Codex profile is busy on the controller or a worker.', code='session_busy')
130
+ yield
131
+ except OSError as error:
132
+ raise CdxError('Unable to hold the controller authentication lock.', code='auth_lock_failed') from error
133
+
134
+
135
+ def run_remote(session, target, version, cwd=None, env_override=None, initial_prompt=None,
136
+ timeout_seconds=None, run_id=None, attempt=1, budget_check=None, remote_prefix=None, **_):
137
+ from .provider_runtime import (
138
+ _build_headless_launch_spec,
139
+ _combine_headless_transcript,
140
+ _headless_artifact_paths,
141
+ _headless_run_info,
142
+ _restore_signal_handlers,
143
+ _route_termination_to_interrupt,
144
+ )
145
+
146
+ home = session['authHome']
147
+ with remote_auth_lock(home):
148
+ auth = temporary_auth(home)
149
+ if auth['tokens'].get('refresh_token') != '':
150
+ raise CdxError('Refusing to send a refresh token to a worker.')
151
+ spec = _build_headless_launch_spec(session, cwd, env_override, initial_prompt)
152
+ if (session.get('launch') or {}).get('network_access'):
153
+ index = spec['args'].index('--') if '--' in spec['args'] else len(spec['args'])
154
+ spec['args'][index:index] = ['-c', 'sandbox_workspace_write.network_access=true']
155
+ config = nonsecret_config(home)
156
+ launch_text = json.dumps(spec['args']) + config
157
+ if any(value and value in launch_text for value in auth['tokens'].values()):
158
+ raise CdxError('Refusing a credential embedded in the remote prompt or configuration.')
159
+ payload = {'auth': auth, 'config': config, 'args': spec['args'], 'cwd': cwd,
160
+ 'version': version, 'expires_at': token_expiry(auth['tokens']['access_token']),
161
+ 'isolate_workspace': bool((session.get('launch') or {}).get('isolate_workspace')),
162
+ 'network': bool((session.get('launch') or {}).get('network_access')),
163
+ 'writable': (session.get('launch') or {}).get('permission') != 'review'}
164
+ rotating = json.loads((pathlib.Path(home) / 'auth.json').read_text())['tokens'].get('refresh_token')
165
+ if rotating and rotating in json.dumps(payload):
166
+ raise CdxError('Refusing to send a refresh token to a worker.')
167
+ paths = _headless_artifact_paths(session, run_id, attempt)
168
+ paths['remote'] = target
169
+ start = datetime.now(timezone.utc)
170
+ child = None
171
+ cancelled = timed_out = budget_exceeded = credential_expired = False
172
+ handlers = _route_termination_to_interrupt()
173
+ try:
174
+ with open(paths['stdout_path'], 'w') as out, open(paths['stderr_path'], 'w') as err:
175
+ child = subprocess.Popen(ssh_command(target, 'run', remote_prefix), env={**os.environ, **(env_override or {})},
176
+ stdin=subprocess.PIPE, stdout=out, stderr=err)
177
+ child.stdin.write(worker_input() + json.dumps(payload).encode() + b'\n')
178
+ child.stdin.flush()
179
+ deadline = time.monotonic() + timeout_seconds if timeout_seconds else None
180
+ while child.poll() is None:
181
+ if time.time() >= payload['expires_at']:
182
+ credential_expired = True
183
+ break
184
+ if budget_check and budget_check(paths):
185
+ budget_exceeded = True
186
+ break
187
+ if deadline and time.monotonic() >= deadline:
188
+ timed_out = True
189
+ break
190
+ time.sleep(0.1)
191
+ except KeyboardInterrupt:
192
+ cancelled = True
193
+ except (OSError, ValueError) as error:
194
+ raise CdxError('Remote SSH transport failed.', code='remote_unavailable') from error
195
+ finally:
196
+ if child:
197
+ # EOF makes the worker stop its process group and remove the home.
198
+ try:
199
+ child.stdin.close()
200
+ except (OSError, BrokenPipeError):
201
+ pass
202
+ try:
203
+ child.wait(timeout=10)
204
+ except subprocess.TimeoutExpired:
205
+ child.terminate()
206
+ try:
207
+ child.wait(timeout=2)
208
+ except subprocess.TimeoutExpired:
209
+ child.kill()
210
+ child.wait()
211
+ _restore_signal_handlers(handlers)
212
+ _combine_headless_transcript(paths)
213
+ info = {**_headless_run_info(paths, spec, start, child.returncode), 'remote': target,
214
+ 'timed_out': timed_out, 'budget_exceeded': budget_exceeded}
215
+ if cancelled:
216
+ error = CdxError('Remote run was cancelled.', 130)
217
+ error.cancelled = True
218
+ error.run_info = {**info, 'returncode': 130}
219
+ raise error
220
+ if credential_expired or time.time() >= payload['expires_at'] or child.returncode == 123:
221
+ error = CdxError('Remote temporary credential expired; start a new run from the controller.', code='remote_credential_expired')
222
+ error.run_info = info
223
+ raise error
224
+ if child.returncode == 255 and not (timed_out or budget_exceeded):
225
+ error = CdxError('Remote SSH connection failed or was lost.', code='remote_unavailable')
226
+ error.run_info = info
227
+ raise error
228
+ if timed_out or budget_exceeded:
229
+ info['returncode'] = 125 if budget_exceeded else 124
230
+ return info
@@ -0,0 +1,233 @@
1
+ """Self-contained Linux worker, framed over SSH stdin before the run payload."""
2
+
3
+ import fcntl
4
+ import json
5
+ import os
6
+ import pathlib
7
+ import shutil
8
+ import signal
9
+ import stat
10
+ import subprocess
11
+ import sys
12
+ import tempfile
13
+ import threading
14
+ import time
15
+
16
+ # Supplied by the controller before this module is executed.
17
+ SANDBOX_SOURCE = globals().get('SANDBOX_SOURCE', '')
18
+
19
+
20
+ def version():
21
+ return subprocess.check_output(["codex", "--version"], stderr=subprocess.DEVNULL, timeout=10).decode().strip()
22
+
23
+
24
+ def storage():
25
+ mounts = [row.split() for row in pathlib.Path('/proc/mounts').read_text().splitlines()]
26
+ location = pathlib.Path('/dev/shm').resolve()
27
+ enclosing = [row for row in mounts if pathlib.Path(row[1]) == location or pathlib.Path(row[1]) in location.parents]
28
+ if not enclosing or max(enclosing, key=lambda row: len(row[1]))[2] != 'tmpfs':
29
+ raise RuntimeError("Remote execution requires memory-backed /dev/shm")
30
+ root = pathlib.Path('/dev/shm') / f'cdx-remote-{os.getuid()}'
31
+ root.mkdir(mode=0o700, exist_ok=True)
32
+ info = root.lstat()
33
+ if not stat.S_ISDIR(info.st_mode) or info.st_uid != os.getuid() or stat.S_IMODE(info.st_mode) != 0o700:
34
+ raise RuntimeError("Unsafe remote credential directory")
35
+ return root
36
+
37
+
38
+ def allocate(root):
39
+ # Serialize sweep and allocation so another launcher cannot sweep a new home.
40
+ with open(root / '.sweep', 'a') as sweep:
41
+ fcntl.flock(sweep, fcntl.LOCK_EX)
42
+ for path in root.glob('run-*'):
43
+ if path.is_symlink() or not path.is_dir():
44
+ continue
45
+ try:
46
+ with open(path / '.live', 'a') as live:
47
+ fcntl.flock(live, fcntl.LOCK_EX | fcntl.LOCK_NB)
48
+ shutil.rmtree(path)
49
+ except (BlockingIOError, FileNotFoundError):
50
+ continue
51
+ home = pathlib.Path(tempfile.mkdtemp(prefix='run-', dir=root))
52
+ live = open(home / '.live', 'a')
53
+ fcntl.flock(live, fcntl.LOCK_EX)
54
+ return home, live
55
+
56
+
57
+ def isolation_probe_command(payload, home, env):
58
+ # Execute exactly the local confinement implementation, shipped over stdin.
59
+ namespace = {'__name__': 'cdx_workspace_sandbox'}
60
+ exec(compile(SANDBOX_SOURCE, 'workspace_sandbox.py', 'exec'), namespace)
61
+ return namespace['sandbox_command'](
62
+ ['codex', '--version'], pathlib.Path(payload['cwd']), network=payload['network'],
63
+ writable=payload['writable'], source=env, auth_home=str(home), provider='codex',
64
+ observation_home=str(home / 'sessions'))
65
+
66
+
67
+ def probe(root, payload):
68
+ if not payload.get('isolate_workspace'):
69
+ print(version())
70
+ return 0
71
+ # Exercise mounts, namespaces and the nft setup with a dummy home before
72
+ # the controller reads or sends a credential. Binary presence is insufficient.
73
+ with tempfile.TemporaryDirectory(prefix='probe-', dir=root) as directory:
74
+ home = pathlib.Path(directory)
75
+ (home / 'auth.json').write_text(json.dumps({'tokens': {'refresh_token': ''}}))
76
+ os.chmod(home / 'auth.json', 0o600)
77
+ (home / 'sessions').mkdir()
78
+ command, env = isolation_probe_command(payload, home, dict(os.environ))
79
+ result = subprocess.run(command, env=env, stdin=subprocess.DEVNULL,
80
+ capture_output=True, timeout=20)
81
+ if result.returncode:
82
+ raise RuntimeError('Remote workspace isolation unavailable')
83
+ print(result.stdout.decode().strip())
84
+ return 0
85
+
86
+
87
+ def main():
88
+ root = storage()
89
+ payload = json.loads(sys.stdin.buffer.readline())
90
+ if sys.argv[-1] == 'probe':
91
+ return probe(root, payload)
92
+ auth = payload['auth']
93
+ if auth['tokens'].get('refresh_token') != '':
94
+ raise RuntimeError('Refusing a rotating remote credential')
95
+ if version() != payload['version']:
96
+ raise RuntimeError('Remote Codex CLI version mismatch')
97
+ child = None
98
+ stopped = threading.Event()
99
+
100
+ def stop(*_):
101
+ stopped.set()
102
+ if child is not None and child.poll() is None:
103
+ try:
104
+ os.killpg(child.pid, signal.SIGTERM)
105
+ child.wait(timeout=3)
106
+ except subprocess.TimeoutExpired:
107
+ os.killpg(child.pid, signal.SIGKILL)
108
+ child.wait()
109
+ except ProcessLookupError:
110
+ pass
111
+
112
+ def disconnected():
113
+ while os.read(0, 1):
114
+ pass
115
+ stop()
116
+
117
+ signal.signal(signal.SIGTERM, stop)
118
+ signal.signal(signal.SIGHUP, stop)
119
+ signal.signal(signal.SIGINT, stop)
120
+ home, live = allocate(root)
121
+ try:
122
+ if stopped.is_set():
123
+ return 130
124
+ os.umask(0o077)
125
+ (home / 'auth.json').write_text(json.dumps(auth))
126
+ os.chmod(home / 'auth.json', 0o600)
127
+ (home / 'config.toml').write_text('cli_auth_credentials_store = "file"\n' + payload['config'])
128
+ env = {key: value for key, value in os.environ.items()
129
+ if key not in ('OPENAI_API_KEY', 'OPENAI_ACCESS_TOKEN', 'CODEX_ACCESS_TOKEN', 'CODEX_API_KEY', 'CODEX_HOME')}
130
+ env['CODEX_HOME'] = str(home)
131
+ command = ['codex', *payload['args']]
132
+ if payload.get('isolate_workspace'):
133
+ (home / 'sessions').mkdir(mode=0o700)
134
+ args = payload['args'][:]
135
+ while '-s' in args:
136
+ index = args.index('-s')
137
+ del args[index:index + 2]
138
+ if '--dangerously-bypass-approvals-and-sandbox' not in args:
139
+ index = args.index('--') if '--' in args else len(args)
140
+ args[index:index] = ['--dangerously-bypass-approvals-and-sandbox']
141
+ namespace = {'__name__': 'cdx_workspace_sandbox'}
142
+ exec(compile(SANDBOX_SOURCE, 'workspace_sandbox.py', 'exec'), namespace)
143
+ command, env = namespace['sandbox_command'](
144
+ ['codex', *args], pathlib.Path(payload['cwd']), network=payload['network'],
145
+ writable=payload['writable'], source=env, auth_home=str(home), provider='codex',
146
+ observation_home=str(home / 'sessions'))
147
+ child = subprocess.Popen(command, cwd=payload['cwd'], env=env,
148
+ stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
149
+ start_new_session=True)
150
+ if stopped.is_set():
151
+ stop()
152
+ threading.Thread(target=disconnected, daemon=True).start()
153
+ secrets = [value for value in auth['tokens'].values() if isinstance(value, str) and value]
154
+
155
+ output_lock = threading.Lock()
156
+ observed = {}
157
+
158
+ def snapshot():
159
+ # Only the fresh per-run home supplies model evidence. No transcript
160
+ # or authentication content leaves the worker.
161
+ with output_lock:
162
+ identifier = observed.get('thread_id')
163
+ usage = observed.get('usage')
164
+ if not identifier or not usage:
165
+ return
166
+ for path in (home / 'sessions').rglob('*.jsonl'):
167
+ if path.is_symlink() or path.stat().st_size > 16 * 1024 * 1024:
168
+ continue
169
+ try:
170
+ records = [json.loads(line) for line in path.read_text().splitlines()]
171
+ except (OSError, ValueError):
172
+ continue
173
+ if not any(row.get('type') == 'session_meta' and row.get('payload', {}).get('id') == identifier for row in records):
174
+ continue
175
+ contexts = [row.get('payload', {}) for row in records if row.get('type') == 'turn_context']
176
+ models = {row.get('model') for row in contexts}
177
+ counts = [row.get('payload', {}).get('info', {}).get('total_token_usage') for row in records
178
+ if row.get('type') == 'event_msg' and row.get('payload', {}).get('type') == 'token_count']
179
+ if (len(models) == 1 and None not in models and counts and counts[-1]
180
+ and all(counts[-1].get(key) == usage.get(key) for key in ('input_tokens', 'output_tokens', 'cached_input_tokens'))):
181
+ event = json.dumps({'type': 'cdx.remote.usage', 'usage': usage, 'model': next(iter(models))})
182
+ for secret in secrets:
183
+ event = event.replace(secret, '[REDACTED]')
184
+ print(event, flush=True)
185
+ return
186
+
187
+ def forward(source, target):
188
+ for line in iter(source.readline, b''):
189
+ for secret in secrets:
190
+ line = line.replace(secret.encode(), b'[REDACTED]')
191
+ with output_lock:
192
+ if target is sys.stdout:
193
+ try:
194
+ event = json.loads(line)
195
+ if event.get('type') == 'thread.started':
196
+ observed['thread_id'] = event.get('thread_id')
197
+ if event.get('type') == 'turn.completed':
198
+ observed['usage'] = event.get('usage')
199
+ except (ValueError, AttributeError):
200
+ pass
201
+ target.buffer.write(line)
202
+ target.buffer.flush()
203
+
204
+ threads = [threading.Thread(target=forward, args=pair) for pair in
205
+ ((child.stdout, sys.stdout), (child.stderr, sys.stderr))]
206
+ for thread in threads:
207
+ thread.start()
208
+ while child.poll() is None:
209
+ try:
210
+ child.wait(timeout=0.25)
211
+ except subprocess.TimeoutExpired:
212
+ snapshot()
213
+ result = child.returncode
214
+ for thread in threads:
215
+ thread.join()
216
+ snapshot()
217
+ if time.time() >= payload['expires_at']:
218
+ print('Remote temporary credential expired; start a new run from the controller.', file=sys.stderr)
219
+ return 123
220
+ return 130 if stopped.is_set() else result
221
+ finally:
222
+ stop()
223
+ shutil.rmtree(home)
224
+ live.close()
225
+
226
+
227
+ if __name__ == '__main__':
228
+ try:
229
+ sys.exit(main())
230
+ except Exception:
231
+ # Never render exception values: input and provider errors may contain credentials.
232
+ print('Remote worker setup or execution failed; check Python, Codex, cwd, /dev/shm and isolation dependencies.', file=sys.stderr)
233
+ sys.exit(126)
@@ -4,7 +4,7 @@ import sys
4
4
  from .cli_args import ARG_CODE_INVALID_VALUE
5
5
  from .errors import CdxArgumentError, CdxError
6
6
  from .provider_runtime import headless_permission_disables_network
7
- from .run_usage import empty_usage, estimate_cost, extract_run_observation
7
+ from .run_usage import cost_fields, empty_usage, estimate_cost, extract_run_observation
8
8
 
9
9
  PROMPT_STDIN = "-"
10
10
 
@@ -169,6 +169,9 @@ def run_launch_payload(api_schema_version, parsed, session, artifacts, cwd=None,
169
169
  "ok": True,
170
170
  "action": "run",
171
171
  "launcher": "cdx",
172
+ **({"remote": parsed["remote"]} if parsed.get("remote") else {}),
173
+ "project": parsed.get("project"),
174
+ "project_source": parsed.get("project_source", "unknown"),
172
175
  "parent_run_id": parsed.get("continue_run"),
173
176
  "conversation_id": conversation_id or run_conversation_id(session),
174
177
  "detached": True,
@@ -219,6 +222,9 @@ def run_result_payload(api_schema_version, ok, parsed, session, run_info=None,
219
222
  "ok": bool(ok),
220
223
  "action": "run",
221
224
  "launcher": "cdx",
225
+ **({"remote": parsed["remote"]} if parsed.get("remote") else {}),
226
+ "project": parsed.get("project"),
227
+ "project_source": parsed.get("project_source", "unknown"),
222
228
  "parent_run_id": parsed.get("continue_run"),
223
229
  "conversation_id": run_conversation_id(session, run_info),
224
230
  "session": session.get("name") if session else None,
@@ -229,6 +235,8 @@ def run_result_payload(api_schema_version, ok, parsed, session, run_info=None,
229
235
  "power": parsed.get("power") or ((session.get("launch") or {}).get("power") if session else None),
230
236
  "cwd": os.path.abspath(parsed.get("cwd") or os.getcwd()),
231
237
  "run_id": run_info.get("run_id"),
238
+ "attempt_id": run_info.get("attempt_id"),
239
+ "attempts": run_info.get("attempts", []),
232
240
  "exit_code": run_info.get("returncode"),
233
241
  "duration_seconds": (run_info.get("duration_ms") / 1000.0) if run_info.get("duration_ms") is not None else None,
234
242
  "transcript_path": run_info.get("transcript_path"),
@@ -236,7 +244,14 @@ def run_result_payload(api_schema_version, ok, parsed, session, run_info=None,
236
244
  "stderr_path": run_info.get("stderr_path"),
237
245
  "usage": usage,
238
246
  "usage_model": usage_model,
247
+ "usage_models": run_info.get("usage_models", [usage_model] if usage_model else []),
248
+ **cost_fields(usage, usage_model, models=run_info.get("usage_models", (run_info.get("usage_evidence") or {}).get("models", [])), attribution=run_info.get("usage_attribution")),
239
249
  "cost_usd": run_info.get("cost_usd", estimate_cost(usage, usage_model)),
250
+ "usage_evidence": run_info.get("usage_evidence"),
251
+ "usage_attribution": run_info.get("usage_attribution"),
252
+ "budget_status": run_info.get("budget_status", "exceeded" if run_info.get("budget_exceeded") else
253
+ "usage_not_measured" if parsed.get("budget") is not None and usage.get("total_tokens") is None else
254
+ "estimated" if parsed.get("budget") is not None else "not_requested"),
240
255
  "budget_usd": parsed.get("budget"),
241
256
  "warnings": warnings,
242
257
  "error": None if ok else {