cdx-manager 0.25.0 → 0.25.2
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.
- package/README.md +1 -1
- package/VERSION +1 -1
- package/changelogs/CHANGELOGS_0_25_1.md +14 -0
- package/changelogs/CHANGELOGS_0_25_2.md +15 -0
- package/checksums/release-archives.json +22 -0
- package/docs/cli.md +90 -4
- package/docs/operations.md +9 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/cli.py +7 -5
- package/src/cli_args.py +5 -2
- package/src/commands/launch.py +13 -5
- package/src/commands/runs.py +88 -24
- package/src/commands/status.py +32 -4
- package/src/history_models.py +10 -1
- package/src/project_identity.py +71 -0
- package/src/provider_runtime.py +35 -5
- package/src/run_command.py +14 -1
- package/src/run_observation.py +93 -0
- package/src/run_registry.py +17 -2
- package/src/run_usage.py +38 -4
- package/src/session_service.py +11 -0
- package/src/session_store.py +62 -0
- package/src/workspace_sandbox.py +6 -2
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Supervisor-owned, content-free snapshots of isolated provider observations."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import stat
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .history_models import _exact_transcript
|
|
10
|
+
from .interactive_usage import MAX_TRANSCRIPT_BYTES
|
|
11
|
+
from .run_usage import _usage_from_dict, extract_run_conversation_id, extract_run_usage
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _open_rollout(root, path):
|
|
15
|
+
"""Walk relative to the pinned attempt directory without following symlinks."""
|
|
16
|
+
parts = Path(path).relative_to(root).parts
|
|
17
|
+
directory = os.open(root, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
|
|
18
|
+
try:
|
|
19
|
+
for part in parts[:-1]:
|
|
20
|
+
child = os.open(part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=directory)
|
|
21
|
+
os.close(directory)
|
|
22
|
+
directory = child
|
|
23
|
+
return os.open(parts[-1], os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=directory)
|
|
24
|
+
finally:
|
|
25
|
+
os.close(directory)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def codex_attempt_evidence(auth_home, stdout_path, started_at):
|
|
29
|
+
"""Read only the exact thread in a fresh attempt's volatile sessions mount.
|
|
30
|
+
|
|
31
|
+
Do not import claimed costs, requested models, messages, or arbitrary files
|
|
32
|
+
from the workspace. The exported object is never writable by the sandbox.
|
|
33
|
+
"""
|
|
34
|
+
usage = extract_run_usage("codex", stdout_path)
|
|
35
|
+
identifier = extract_run_conversation_id("codex", stdout_path)
|
|
36
|
+
evidence = {"source": "isolated_codex_rollout", "stdout_path": str(stdout_path),
|
|
37
|
+
"thread_id": identifier, "started_at": started_at, "models": [],
|
|
38
|
+
"turns": [], "usage": usage}
|
|
39
|
+
if not identifier:
|
|
40
|
+
return evidence
|
|
41
|
+
path, error = _exact_transcript("codex", auth_home, identifier)
|
|
42
|
+
if error or not path:
|
|
43
|
+
return evidence
|
|
44
|
+
models = set()
|
|
45
|
+
turns = []
|
|
46
|
+
identity = False
|
|
47
|
+
complete_models = True
|
|
48
|
+
counters = None
|
|
49
|
+
ended_at = datetime.now(timezone.utc)
|
|
50
|
+
try:
|
|
51
|
+
descriptor = _open_rollout(auth_home, path)
|
|
52
|
+
with os.fdopen(descriptor, encoding="utf-8") as handle:
|
|
53
|
+
if not stat.S_ISREG(os.fstat(handle.fileno()).st_mode):
|
|
54
|
+
return evidence
|
|
55
|
+
text = handle.read(MAX_TRANSCRIPT_BYTES + 1)
|
|
56
|
+
if len(text) > MAX_TRANSCRIPT_BYTES:
|
|
57
|
+
return evidence
|
|
58
|
+
for line in text.splitlines():
|
|
59
|
+
record = json.loads(line)
|
|
60
|
+
payload = record.get("payload") or {}
|
|
61
|
+
if record.get("type") == "session_meta":
|
|
62
|
+
if payload.get("id") != identifier:
|
|
63
|
+
return evidence
|
|
64
|
+
identity = True
|
|
65
|
+
if record.get("type") == "event_msg" and payload.get("type") == "token_count":
|
|
66
|
+
counters = _usage_from_dict((payload.get("info") or {}).get("total_token_usage") or {}, provider="codex")
|
|
67
|
+
if record.get("type") != "turn_context":
|
|
68
|
+
continue
|
|
69
|
+
timestamp = record.get("timestamp")
|
|
70
|
+
if (not timestamp or datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
|
|
71
|
+
< datetime.fromisoformat(started_at.replace("Z", "+00:00"))):
|
|
72
|
+
complete_models = False
|
|
73
|
+
continue
|
|
74
|
+
if datetime.fromisoformat(timestamp.replace("Z", "+00:00")) > ended_at:
|
|
75
|
+
complete_models = False
|
|
76
|
+
continue
|
|
77
|
+
model = payload.get("model")
|
|
78
|
+
if not isinstance(model, str) or not model:
|
|
79
|
+
complete_models = False
|
|
80
|
+
continue
|
|
81
|
+
models.add(model)
|
|
82
|
+
turn_id = payload.get("turn_id")
|
|
83
|
+
turns.append({"turn_id": turn_id if isinstance(turn_id, str) else None, "timestamp": timestamp, "model": model})
|
|
84
|
+
except (OSError, ValueError, TypeError, AttributeError):
|
|
85
|
+
return evidence
|
|
86
|
+
# Rollout counters must corroborate exec's measured counters. A free-form
|
|
87
|
+
# model/cost claim or a previous conversation's context is insufficient.
|
|
88
|
+
matches = counters is not None and all(value is None or counters.get(key) == value for key, value in usage.items())
|
|
89
|
+
if identity and matches:
|
|
90
|
+
evidence.update(models=sorted(models) if complete_models else [], turns=turns, ended_at=ended_at.isoformat())
|
|
91
|
+
if usage.get("total_tokens") is None:
|
|
92
|
+
evidence["measurement_usage"] = counters
|
|
93
|
+
return evidence
|
package/src/run_registry.py
CHANGED
|
@@ -244,7 +244,7 @@ class RunRegistry:
|
|
|
244
244
|
_check_capacity(_read_registry(self.path), max_parallel)
|
|
245
245
|
|
|
246
246
|
def start(self, run_id, *, kind, session, provider, model, cwd, artifacts=None, label=None, max_parallel=None,
|
|
247
|
-
parent_run_id=None, conversation_id=None, launch=None, reasoning_effort=None):
|
|
247
|
+
parent_run_id=None, conversation_id=None, launch=None, reasoning_effort=None, project=None, project_source="unknown"):
|
|
248
248
|
with _registry_lock(self.path):
|
|
249
249
|
data = _read_registry(self.path)
|
|
250
250
|
data["runs"] = [run for run in data["runs"] if run.get("run_id") != run_id]
|
|
@@ -254,7 +254,8 @@ class RunRegistry:
|
|
|
254
254
|
_check_capacity(data, max_parallel)
|
|
255
255
|
record = _base_record(run_id, kind=kind, session=session, provider=provider, model=model, cwd=cwd, artifacts=artifacts)
|
|
256
256
|
record.update(parent_run_id=parent_run_id, conversation_id=conversation_id,
|
|
257
|
-
launch=dict(launch or {}), reasoning_effort=reasoning_effort
|
|
257
|
+
launch=dict(launch or {}), reasoning_effort=reasoning_effort,
|
|
258
|
+
project=project, project_source=project_source)
|
|
258
259
|
if label:
|
|
259
260
|
record["label"] = label
|
|
260
261
|
data["runs"].insert(0, record)
|
|
@@ -280,6 +281,18 @@ class RunRegistry:
|
|
|
280
281
|
return run
|
|
281
282
|
return None
|
|
282
283
|
|
|
284
|
+
def record_attempt(self, run_id, artifacts):
|
|
285
|
+
"""Keep live tail paths on this supervisor's current attempt."""
|
|
286
|
+
with _registry_lock(self.path):
|
|
287
|
+
data = _read_registry(self.path)
|
|
288
|
+
for run in data["runs"]:
|
|
289
|
+
if run.get("run_id") == run_id and run.get("status") == "running":
|
|
290
|
+
run["attempt_id"] = artifacts.get("attempt_id")
|
|
291
|
+
run["artifacts"] = {key: artifacts.get(key) for key in ("transcript_path", "stdout_path", "stderr_path")}
|
|
292
|
+
_write_registry(self.path, data)
|
|
293
|
+
return run
|
|
294
|
+
return None
|
|
295
|
+
|
|
283
296
|
def record_background(self, run_id, *, path, provider_session_id=None, provider_agent_id=None):
|
|
284
297
|
with _registry_lock(self.path):
|
|
285
298
|
data = _read_registry(self.path)
|
|
@@ -376,6 +389,8 @@ class RunRegistry:
|
|
|
376
389
|
# used to store whichever shape the producing reader emitted.
|
|
377
390
|
run["usage"] = coerce_usage(final_payload.get("usage"), provider=run.get("provider"))
|
|
378
391
|
run["usage_model"] = final_payload.get("usage_model")
|
|
392
|
+
for key in ("usage_evidence", "usage_models", "attempt_id", "attempts", "cost_usd", "cost_status", "cost_reason", "budget_status"):
|
|
393
|
+
run[key] = final_payload.get(key)
|
|
379
394
|
run["conversation_id"] = final_payload.get("conversation_id") or run.get("conversation_id")
|
|
380
395
|
run["parent_run_id"] = final_payload.get("parent_run_id") or run.get("parent_run_id")
|
|
381
396
|
if "reasoning_effort" in final_payload:
|
package/src/run_usage.py
CHANGED
|
@@ -363,6 +363,25 @@ def estimate_cost(usage, model, prices=None):
|
|
|
363
363
|
return sum(present) / 1_000_000
|
|
364
364
|
|
|
365
365
|
|
|
366
|
+
def cost_fields(usage, model, prices=None, *, models=(), attribution=None):
|
|
367
|
+
"""Explain nullable estimates consistently on every JSON surface."""
|
|
368
|
+
cost = estimate_cost(usage, model, prices)
|
|
369
|
+
reason = None
|
|
370
|
+
if cost is None:
|
|
371
|
+
if attribution == "continuation_usage_unavailable":
|
|
372
|
+
reason = "attempt_usage_unavailable"
|
|
373
|
+
elif not _has_usage(usage):
|
|
374
|
+
reason = "usage_not_measured"
|
|
375
|
+
elif len(set(models)) > 1:
|
|
376
|
+
reason = "mixed_models_without_counters"
|
|
377
|
+
elif not model:
|
|
378
|
+
reason = "model_not_observed"
|
|
379
|
+
else:
|
|
380
|
+
reason = "model_price_unknown"
|
|
381
|
+
return {"cost_usd": cost, "cost_status": "estimated" if cost is not None else "unknown",
|
|
382
|
+
"cost_reason": reason}
|
|
383
|
+
|
|
384
|
+
|
|
366
385
|
def _long_ttl_writes(usage):
|
|
367
386
|
"""One-hour cache writes, bounded by the writes they are a part of."""
|
|
368
387
|
long_ttl = usage.get("cache_creation_1h_tokens")
|
|
@@ -475,7 +494,7 @@ def extract_run_usage(provider, stdout_path):
|
|
|
475
494
|
return extract_run_observation(provider, stdout_path)[0]
|
|
476
495
|
|
|
477
496
|
|
|
478
|
-
def extract_run_observation(provider, stdout_path, auth_home=None):
|
|
497
|
+
def extract_run_observation(provider, stdout_path, auth_home=None, *, evidence=None, models_out=None):
|
|
479
498
|
if not stdout_path or not provider:
|
|
480
499
|
return empty_usage(), None
|
|
481
500
|
if provider not in SUPPORTED_PROVIDERS:
|
|
@@ -493,6 +512,10 @@ def extract_run_observation(provider, stdout_path, auth_home=None):
|
|
|
493
512
|
return empty_usage(), None
|
|
494
513
|
|
|
495
514
|
usage = _extract_usage_from_records(records, provider=provider)
|
|
515
|
+
if (provider == "codex" and evidence is not None and evidence.get("stdout_path") == str(stdout_path)
|
|
516
|
+
and evidence.get("usage") == usage and evidence.get("thread_id") == extract_run_conversation_id(provider, stdout_path)
|
|
517
|
+
and not _has_usage(usage)):
|
|
518
|
+
usage = evidence.get("measurement_usage") or usage
|
|
496
519
|
if not _has_usage(usage):
|
|
497
520
|
return empty_usage(), None
|
|
498
521
|
models = set()
|
|
@@ -506,8 +529,18 @@ def extract_run_observation(provider, stdout_path, auth_home=None):
|
|
|
506
529
|
model_usage = record.get("modelUsage")
|
|
507
530
|
if isinstance(model_usage, dict):
|
|
508
531
|
models.update(model_usage)
|
|
509
|
-
if provider == "codex" and
|
|
532
|
+
if provider == "codex" and evidence is not None:
|
|
533
|
+
# Only the supervisor supplies this snapshot, never a file in the checkout.
|
|
534
|
+
if (evidence.get("stdout_path") == str(stdout_path)
|
|
535
|
+
and evidence.get("thread_id") == extract_run_conversation_id(provider, stdout_path)
|
|
536
|
+
and (evidence.get("measurement_usage") or evidence.get("usage")) == usage):
|
|
537
|
+
models.update(evidence.get("models") or [])
|
|
538
|
+
else:
|
|
539
|
+
return usage, None
|
|
540
|
+
elif provider == "codex" and auth_home:
|
|
510
541
|
models.update(_codex_run_models(records, auth_home))
|
|
542
|
+
if models_out is not None:
|
|
543
|
+
models_out.extend(sorted(models))
|
|
511
544
|
return usage, next(iter(models)) if len(models) == 1 else None
|
|
512
545
|
|
|
513
546
|
|
|
@@ -539,8 +572,9 @@ def _codex_run_models(records, auth_home):
|
|
|
539
572
|
continue
|
|
540
573
|
payload = record.get("payload")
|
|
541
574
|
model = payload.get("model") if isinstance(payload, dict) else None
|
|
542
|
-
if isinstance(model, str)
|
|
543
|
-
|
|
575
|
+
if not isinstance(model, str) or not model:
|
|
576
|
+
return set()
|
|
577
|
+
models.add(model)
|
|
544
578
|
except OSError:
|
|
545
579
|
return set()
|
|
546
580
|
return models
|
package/src/session_service.py
CHANGED
|
@@ -777,6 +777,14 @@ def backfill_history_models(store, base_dir, dry_run=True):
|
|
|
777
777
|
)
|
|
778
778
|
|
|
779
779
|
|
|
780
|
+
def backfill_history_projects(store, base_dir, mapping_path=None, dry_run=True):
|
|
781
|
+
from .history_models import read_run_reports
|
|
782
|
+
from .project_identity import load_mapping
|
|
783
|
+
|
|
784
|
+
mapping = load_mapping(mapping_path) if mapping_path else {}
|
|
785
|
+
return store["backfill_launch_projects"](mapping, read_run_reports(base_dir), dry_run=dry_run)
|
|
786
|
+
|
|
787
|
+
|
|
780
788
|
def refresh_interactive_usage_models(store, dry_run=True):
|
|
781
789
|
"""Replace a placeholder model with the exact transcript's real model."""
|
|
782
790
|
from .interactive_usage import MATCH_CONVERSATION_ID, extract_interactive_usage
|
|
@@ -813,6 +821,8 @@ def start_session_runtime(store, name, payload=None):
|
|
|
813
821
|
"command": payload.get("command"),
|
|
814
822
|
"label": payload.get("label"),
|
|
815
823
|
"cwd": payload.get("cwd"),
|
|
824
|
+
"project": payload.get("project"),
|
|
825
|
+
"project_source": payload.get("project_source", "unknown"),
|
|
816
826
|
"transcriptPath": payload.get("transcript_path") or payload.get("transcriptPath"),
|
|
817
827
|
}
|
|
818
828
|
|
|
@@ -1010,6 +1020,7 @@ def create_session_service(options=None):
|
|
|
1010
1020
|
"backfill_interactive_usage": partial(backfill_interactive_usage, store),
|
|
1011
1021
|
"refresh_interactive_usage_models": partial(refresh_interactive_usage_models, store),
|
|
1012
1022
|
"backfill_history_models": partial(backfill_history_models, store, base_dir),
|
|
1023
|
+
"backfill_history_projects": partial(backfill_history_projects, store, base_dir),
|
|
1013
1024
|
"update_auth_state": partial(update_auth_state, store),
|
|
1014
1025
|
"get_status_row": partial(get_status_row, store, base_dir, env, fetch_codex_status),
|
|
1015
1026
|
"get_status_rows": partial(get_status_rows, store, base_dir, env, fetch_codex_status),
|
package/src/session_store.py
CHANGED
|
@@ -395,6 +395,67 @@ def create_session_store(base_dir):
|
|
|
395
395
|
result["backfilled"] = recovered
|
|
396
396
|
return result
|
|
397
397
|
|
|
398
|
+
def backfill_launch_projects(mapping, reports, dry_run=True):
|
|
399
|
+
"""Plan and atomically apply evidence-only attribution under the history lock."""
|
|
400
|
+
import uuid
|
|
401
|
+
|
|
402
|
+
from .project_identity import recorded_project
|
|
403
|
+
|
|
404
|
+
result = {"dry_run": dry_run, "recovered": 0, "unchanged": 0, "unresolved": 0,
|
|
405
|
+
"conflicting": 0, "applied": 0, "backup_path": None}
|
|
406
|
+
with _file_lock(lock_file):
|
|
407
|
+
try:
|
|
408
|
+
with open(launch_history_file, "rb") as handle:
|
|
409
|
+
original = handle.read()
|
|
410
|
+
except FileNotFoundError:
|
|
411
|
+
original = b""
|
|
412
|
+
lines = original.splitlines(keepends=True)
|
|
413
|
+
entries = []
|
|
414
|
+
for line in lines:
|
|
415
|
+
try:
|
|
416
|
+
entry = json.loads(line)
|
|
417
|
+
except (ValueError, UnicodeError):
|
|
418
|
+
entry = None
|
|
419
|
+
entries.append(entry if isinstance(entry, dict) else None)
|
|
420
|
+
run_ids = {entry.get("run_id") for entry in entries if entry and isinstance(entry.get("run_id"), str)}
|
|
421
|
+
missing = set(mapping) - run_ids
|
|
422
|
+
if missing:
|
|
423
|
+
raise CdxError(f"Project mapping references unknown exact run ids: {', '.join(sorted(missing))}")
|
|
424
|
+
evidence = {}
|
|
425
|
+
for record in [*reports, *[entry for entry in entries if entry]]:
|
|
426
|
+
run_id = record.get("run_id")
|
|
427
|
+
project = recorded_project(record)
|
|
428
|
+
if isinstance(run_id, str) and project:
|
|
429
|
+
evidence.setdefault(run_id, set()).add(project)
|
|
430
|
+
for run_id, project in mapping.items():
|
|
431
|
+
evidence.setdefault(run_id, set()).add(project)
|
|
432
|
+
rewritten = []
|
|
433
|
+
for line, entry in zip(lines, entries):
|
|
434
|
+
if entry is not None:
|
|
435
|
+
run_id = entry.get("run_id")
|
|
436
|
+
candidates = evidence.get(run_id, set()) if isinstance(run_id, str) else set()
|
|
437
|
+
known = recorded_project(entry)
|
|
438
|
+
if len(candidates) > 1:
|
|
439
|
+
result["conflicting"] += 1
|
|
440
|
+
elif known:
|
|
441
|
+
result["unchanged"] += 1
|
|
442
|
+
elif candidates:
|
|
443
|
+
result["recovered"] += 1
|
|
444
|
+
entry.update(project=next(iter(candidates)),
|
|
445
|
+
project_source="operator_mapping" if run_id in mapping else "run_report")
|
|
446
|
+
line = (json.dumps(entry, separators=(",", ":")) + "\n").encode("utf-8")
|
|
447
|
+
else:
|
|
448
|
+
result["unresolved"] += 1
|
|
449
|
+
rewritten.append(line)
|
|
450
|
+
# A conflicting batch is entirely read-only, including otherwise valid rows.
|
|
451
|
+
if not dry_run and result["recovered"] and not result["conflicting"]:
|
|
452
|
+
backup = f"{launch_history_file}.projects-{uuid.uuid4().hex}.bak"
|
|
453
|
+
atomic_write(backup, original)
|
|
454
|
+
atomic_write(launch_history_file, b"".join(rewritten))
|
|
455
|
+
_fsync_directory(state_dir)
|
|
456
|
+
result.update(backup_path=backup, applied=result["recovered"])
|
|
457
|
+
return result
|
|
458
|
+
|
|
398
459
|
def list_launch_history(session_name=None, limit=20):
|
|
399
460
|
with _file_lock(lock_file):
|
|
400
461
|
try:
|
|
@@ -433,5 +494,6 @@ def create_session_store(base_dir):
|
|
|
433
494
|
"drop_unvouched_usage": drop_unvouched_usage,
|
|
434
495
|
"backfill_launch_usage": backfill_launch_usage,
|
|
435
496
|
"backfill_launch_models": backfill_launch_models,
|
|
497
|
+
"backfill_launch_projects": backfill_launch_projects,
|
|
436
498
|
"list_launch_history": list_launch_history,
|
|
437
499
|
}
|
package/src/workspace_sandbox.py
CHANGED
|
@@ -56,12 +56,13 @@ def clean_env(source: dict) -> dict:
|
|
|
56
56
|
GIT_CONFIG_COUNT="1", GIT_CONFIG_KEY_0="credential.helper", GIT_CONFIG_VALUE_0="",
|
|
57
57
|
GH_CONFIG_DIR="/home/agent/.config/gh", NPM_CONFIG_CACHE="/cache/npm",
|
|
58
58
|
PIP_CACHE_DIR="/cache/pip", UV_CACHE_DIR="/cache/uv",
|
|
59
|
-
|
|
59
|
+
# Cache users have different mount paths; their GC cannot identify each other's browsers.
|
|
60
|
+
PLAYWRIGHT_BROWSERS_PATH="/cache/playwright", PLAYWRIGHT_SKIP_BROWSER_GC="1", CI="1")
|
|
60
61
|
return env
|
|
61
62
|
|
|
62
63
|
|
|
63
64
|
def sandbox_command(command: list[str], tree: Path, *, network: bool, writable: bool = True,
|
|
64
|
-
source: dict | None = None, auth_home: str = "", provider: str = "") -> tuple[list[str], dict]:
|
|
65
|
+
source: dict | None = None, auth_home: str = "", provider: str = "", observation_home: str = "") -> tuple[list[str], dict]:
|
|
65
66
|
source = os.environ if source is None else source
|
|
66
67
|
binary = shutil.which(command[0], path=source.get("PATH"))
|
|
67
68
|
bwrap = shutil.which("bwrap", path=source.get("PATH"))
|
|
@@ -126,6 +127,9 @@ def sandbox_command(command: list[str], tree: Path, *, network: bool, writable:
|
|
|
126
127
|
env = clean_env(source)
|
|
127
128
|
if provider == "codex":
|
|
128
129
|
env["CODEX_HOME"] = "/home/agent/.codex"
|
|
130
|
+
if observation_home:
|
|
131
|
+
# Fresh, volatile attempt storage; no host profile is exposed.
|
|
132
|
+
args += ["--bind", observation_home, "/home/agent/.codex/sessions"]
|
|
129
133
|
args += ["--ro-bind", str(Path(auth_home) / "auth.json"), "/home/agent/.codex/auth.json"]
|
|
130
134
|
elif provider == "claude":
|
|
131
135
|
token = source.get("CLAUDE_CODE_OAUTH_TOKEN")
|