ccx-native 0.0.1 → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +241 -4
- package/bin/ccx +55 -2
- package/ccx/__init__.py +163 -0
- package/ccx/__main__.py +28 -0
- package/ccx/claude/__init__.py +0 -0
- package/ccx/claude/analytics.py +466 -0
- package/ccx/claude/attach.py +762 -0
- package/ccx/claude/context.py +465 -0
- package/ccx/claude/delivery.py +806 -0
- package/ccx/claude/external.py +253 -0
- package/ccx/claude/host/__init__.py +0 -0
- package/ccx/claude/host/events.py +297 -0
- package/ccx/claude/host/hooks.py +438 -0
- package/ccx/claude/host/pty.py +250 -0
- package/ccx/claude/host/session.py +5184 -0
- package/ccx/claude/host/subagents.py +169 -0
- package/ccx/claude/host/successor.py +267 -0
- package/ccx/claude/managed_turns.py +240 -0
- package/ccx/claude/oauth.py +176 -0
- package/ccx/claude/settings.py +339 -0
- package/ccx/claude/spawn.py +557 -0
- package/ccx/claude/tasks.py +72 -0
- package/ccx/claude/telemetry.py +282 -0
- package/ccx/claude/transcript.py +2281 -0
- package/ccx/claude/usage.py +837 -0
- package/ccx/claude/usage_control.py +152 -0
- package/ccx/cli/__init__.py +0 -0
- package/ccx/cli/codex.py +1009 -0
- package/ccx/cli/commands/__init__.py +0 -0
- package/ccx/cli/commands/_common.py +407 -0
- package/ccx/cli/commands/control.py +161 -0
- package/ccx/cli/commands/create.py +669 -0
- package/ccx/cli/commands/read.py +1020 -0
- package/ccx/cli/commands/turns.py +1081 -0
- package/ccx/cli/controller.py +757 -0
- package/ccx/cli/main.py +301 -0
- package/ccx/cli/parser.py +1324 -0
- package/ccx/cli/skills.py +215 -0
- package/ccx/cli/statusline.py +160 -0
- package/ccx/client.py +1900 -0
- package/ccx/codex/__init__.py +0 -0
- package/ccx/codex/attach_tty.py +267 -0
- package/ccx/codex/client.py +732 -0
- package/ccx/codex/completion.py +121 -0
- package/ccx/codex/control.py +288 -0
- package/ccx/codex/daemon/__init__.py +0 -0
- package/ccx/codex/daemon/__main__.py +4 -0
- package/ccx/codex/daemon/daemon.py +1556 -0
- package/ccx/codex/daemon/models.py +140 -0
- package/ccx/codex/daemon/native.py +1590 -0
- package/ccx/codex/daemon/release.py +367 -0
- package/ccx/codex/daemon/worker.py +2396 -0
- package/ccx/codex/delivery.py +758 -0
- package/ccx/codex/delivery_recovery.py +232 -0
- package/ccx/codex/evidence.py +827 -0
- package/ccx/codex/history.py +158 -0
- package/ccx/codex/notify_history.py +140 -0
- package/ccx/codex/ops.py +666 -0
- package/ccx/codex/paths.py +70 -0
- package/ccx/codex/protocol.py +805 -0
- package/ccx/codex/provenance.py +105 -0
- package/ccx/codex/read.py +29 -0
- package/ccx/codex/records.py +51 -0
- package/ccx/codex/reports.py +177 -0
- package/ccx/codex/runtime.py +1412 -0
- package/ccx/codex/settle.py +363 -0
- package/ccx/codex/telemetry.py +792 -0
- package/ccx/codex/types.py +659 -0
- package/ccx/codex/worker_environment.py +25 -0
- package/ccx/constants.py +288 -0
- package/ccx/controllers/__init__.py +0 -0
- package/ccx/controllers/controller.py +1449 -0
- package/ccx/controllers/live_vars.py +1706 -0
- package/ccx/controllers/live_vars_runner.py +216 -0
- package/ccx/controllers/notify.py +213 -0
- package/ccx/controllers/orchestrator.py +193 -0
- package/ccx/controllers/policy.py +57 -0
- package/ccx/controllers/store.py +681 -0
- package/ccx/errors.py +634 -0
- package/ccx/notify/__init__.py +0 -0
- package/ccx/notify/__main__.py +5 -0
- package/ccx/notify/client.py +116 -0
- package/ccx/notify/daemon.py +480 -0
- package/ccx/notify/deliver.py +304 -0
- package/ccx/notify/paths.py +51 -0
- package/ccx/notify/registry.py +649 -0
- package/ccx/notify/select.py +750 -0
- package/ccx/notify/settle.py +684 -0
- package/ccx/ops/__init__.py +0 -0
- package/ccx/ops/background.py +115 -0
- package/ccx/ops/cancel.py +289 -0
- package/ccx/ops/codex_import.py +96 -0
- package/ccx/ops/common.py +397 -0
- package/ccx/ops/compact.py +337 -0
- package/ccx/ops/compact_ops.py +550 -0
- package/ccx/ops/context_events.py +48 -0
- package/ccx/ops/conversation.py +1540 -0
- package/ccx/ops/conversation_paging.py +1338 -0
- package/ccx/ops/create.py +56 -0
- package/ccx/ops/creation.py +952 -0
- package/ccx/ops/creation_steps.py +579 -0
- package/ccx/ops/event_feed.py +710 -0
- package/ccx/ops/forking.py +711 -0
- package/ccx/ops/input.py +556 -0
- package/ccx/ops/maintenance.py +295 -0
- package/ccx/ops/report.py +770 -0
- package/ccx/ops/semantics.py +490 -0
- package/ccx/ops/send.py +961 -0
- package/ccx/ops/steer_ops.py +867 -0
- package/ccx/ops/terminate.py +383 -0
- package/ccx/ops/turn_reports.py +306 -0
- package/ccx/ops/wait.py +762 -0
- package/ccx/ops/worker_events.py +636 -0
- package/ccx/rpc/__init__.py +0 -0
- package/ccx/rpc/local_rpc.py +1602 -0
- package/ccx/rpc/rpc.py +559 -0
- package/ccx/rpc/serve.py +374 -0
- package/ccx/rpc/transport.py +409 -0
- package/ccx/rpc/view.py +125 -0
- package/ccx/store/__init__.py +0 -0
- package/ccx/store/discovery.py +170 -0
- package/ccx/store/identity.py +105 -0
- package/ccx/store/journal_io.py +79 -0
- package/ccx/store/liveness.py +194 -0
- package/ccx/store/paths.py +254 -0
- package/ccx/store/record_cache.py +40 -0
- package/ccx/store/records.py +309 -0
- package/ccx/store/root.py +804 -0
- package/ccx/store/runtime_presence.py +137 -0
- package/ccx/store/store.py +2332 -0
- package/ccx/store/worker_state.py +86 -0
- package/ccx/system/__init__.py +0 -0
- package/ccx/system/bounded.py +296 -0
- package/ccx/system/cwd_paths.py +25 -0
- package/ccx/system/engine_binary.py +59 -0
- package/ccx/system/executable.py +50 -0
- package/ccx/system/oslayer.py +667 -0
- package/ccx/system/pipes.py +46 -0
- package/ccx/system/sanitize.py +43 -0
- package/ccx/types.py +705 -0
- package/ccx/version.py +29 -0
- package/ccx/workers.py +2081 -0
- package/ccx.py +9 -0
- package/package.json +39 -7
- package/pyproject.toml +87 -0
- package/skills/ccx/SKILL.md +155 -0
- package/skills/ccx/references/controllers.md +244 -0
- package/skills/ccx/references/troubleshooting.md +94 -0
- package/skills/ccx/references/variables.md +62 -0
package/ccx/cli/main.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from ccx.controllers.policy import reject_codex_blocking_call
|
|
7
|
+
from ccx.cli.parser import build_parser, ccx_version
|
|
8
|
+
from ccx.system.pipes import quiet_broken_stdout
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Only ALREADY-ENABLED user-facing worker-reference arguments. Never infer
|
|
12
|
+
# from a generic "target" name: controller run IDs, delivery/turn/request IDs,
|
|
13
|
+
# creation worker_id, native hook IDs and protocol rpc are different domains.
|
|
14
|
+
_USER_WORKER_FIELDS = {
|
|
15
|
+
"new": ("vars_from",),
|
|
16
|
+
"fork": ("target", "vars_from"),
|
|
17
|
+
"resume": ("target",),
|
|
18
|
+
"send": ("target", "vars_from"),
|
|
19
|
+
"report": ("targets",),
|
|
20
|
+
"context": ("target",),
|
|
21
|
+
"peek": ("target", "extra_shorts"),
|
|
22
|
+
"set": ("target",),
|
|
23
|
+
"vars": ("target",),
|
|
24
|
+
"events": ("target",),
|
|
25
|
+
"logs": ("target",),
|
|
26
|
+
"wait": ("target",),
|
|
27
|
+
"answer": ("target",),
|
|
28
|
+
"approve": ("target",),
|
|
29
|
+
"reject": ("target",),
|
|
30
|
+
"respond": ("target",),
|
|
31
|
+
"stop": ("target",),
|
|
32
|
+
"kill": ("target",),
|
|
33
|
+
"attach": ("target",),
|
|
34
|
+
"steer": ("target",),
|
|
35
|
+
"reconcile": ("target",),
|
|
36
|
+
"compact": ("target",),
|
|
37
|
+
"notify": ("target",),
|
|
38
|
+
}
|
|
39
|
+
def _preflight_worker_references(args, store) -> None:
|
|
40
|
+
"""Canonicalize all user prefix fields before any handler/effect starts."""
|
|
41
|
+
if (args.cmd in {"new", "fork", "send"}
|
|
42
|
+
and getattr(args, "var", None) is not None
|
|
43
|
+
and getattr(args, "vars_from", None) is not None):
|
|
44
|
+
# Existing mutual-exclusion refusal must win before discovery of an
|
|
45
|
+
# unrelated --vars-from owner; no handler may claim a child or send.
|
|
46
|
+
return
|
|
47
|
+
if args.cmd == "controller":
|
|
48
|
+
# Controller target/name/run IDs are a separate namespace. Only an
|
|
49
|
+
# explicit worker input is CLI-discovered, before any handler effect.
|
|
50
|
+
if (getattr(args, "controller_cmd", None) in {"input", "resume"}
|
|
51
|
+
and getattr(args, "worker", None) is not None):
|
|
52
|
+
from ccx.store.discovery import resolve_worker
|
|
53
|
+
args.worker = resolve_worker(args.worker)
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
if (args.cmd == "peek" and args.status
|
|
57
|
+
and getattr(args, "extra_shorts", None)):
|
|
58
|
+
# Read-only multi-target status isolates failures in its row reader.
|
|
59
|
+
# Single-target and effectful verbs still preflight atomically.
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
from ccx.store.discovery import resolve_cli_fields
|
|
63
|
+
|
|
64
|
+
fields = _USER_WORKER_FIELDS.get(args.cmd, ())
|
|
65
|
+
if fields:
|
|
66
|
+
# Preserve the report command's multi-target per-row error results.
|
|
67
|
+
multi_report = (args.cmd == "report" and len(args.targets) > 1)
|
|
68
|
+
resolve_cli_fields(args, store, fields, check_exact=not multi_report)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _native_handler(args, store):
|
|
72
|
+
"""Choose only existing native CLI handlers, after complete prefix preflight."""
|
|
73
|
+
from ccx.cli import codex as codex_commands
|
|
74
|
+
from ccx.constants import EXIT_ERROR
|
|
75
|
+
from ccx.claude.usage import _caps_from_args
|
|
76
|
+
|
|
77
|
+
def refuse(message):
|
|
78
|
+
sys.stderr.write(f"ccx {args.cmd}: {message}; no native effect submitted\n")
|
|
79
|
+
raise SystemExit(EXIT_ERROR)
|
|
80
|
+
|
|
81
|
+
if args.cmd == "usage":
|
|
82
|
+
if args.engine == "claude":
|
|
83
|
+
if args.tokens:
|
|
84
|
+
refuse("--tokens is Codex-only")
|
|
85
|
+
if args.set_source and args.cached:
|
|
86
|
+
refuse("--set-source cannot combine with --cached")
|
|
87
|
+
elif args.engine == "codex" and (args.cached or args.summary or args.set_source):
|
|
88
|
+
refuse("--cached/--summary/--set-source are Claude-only")
|
|
89
|
+
if args.engine is None and args.set_source and (args.cached or args.tokens):
|
|
90
|
+
refuse("--set-source cannot combine with --cached or --tokens")
|
|
91
|
+
return codex_commands.cmd_usage if args.engine == "codex" else args.func
|
|
92
|
+
|
|
93
|
+
if args.cmd in {"answer", "respond"}:
|
|
94
|
+
for target in [args.target]:
|
|
95
|
+
if store.resolve(target).engine != "claude":
|
|
96
|
+
refuse("this operation is Claude-only")
|
|
97
|
+
return args.func
|
|
98
|
+
|
|
99
|
+
target_verbs = {
|
|
100
|
+
"fork", "send", "steer", "reconcile", "wait", "notify",
|
|
101
|
+
"approve", "reject", "set", "compact", "logs",
|
|
102
|
+
}
|
|
103
|
+
if args.cmd not in target_verbs:
|
|
104
|
+
return args.func
|
|
105
|
+
# Ordinary normalized history has its own typed owner/error handling.
|
|
106
|
+
if args.cmd == "logs" and args.format == "conversation" and not args.jsonl:
|
|
107
|
+
return args.func
|
|
108
|
+
from ccx.store.identity import IdentityError, UnknownWorker
|
|
109
|
+
try:
|
|
110
|
+
owner = store.resolve(args.target)
|
|
111
|
+
except IdentityError as error:
|
|
112
|
+
if args.cmd == "logs":
|
|
113
|
+
return args.func
|
|
114
|
+
sys.stderr.write(f"ccx {args.cmd}: original CCX owner unavailable: {error}\n")
|
|
115
|
+
raise SystemExit(2 if isinstance(error, UnknownWorker) else EXIT_ERROR) from error
|
|
116
|
+
codex = owner.engine == "codex"
|
|
117
|
+
settle = bool(getattr(args, "settle", False))
|
|
118
|
+
notify = bool(getattr(args, "notify_parent", False))
|
|
119
|
+
if args.cmd in {"fork", "send", "steer"}:
|
|
120
|
+
if codex and "opus" in (_caps_from_args(args) or {}):
|
|
121
|
+
refuse("--cap-opus is Claude-only")
|
|
122
|
+
if args.cmd in {"fork", "send", "compact", "approve", "reject"}:
|
|
123
|
+
if settle and (not codex or not notify):
|
|
124
|
+
refuse("--settle requires a Codex worker and --notify-parent")
|
|
125
|
+
if args.cmd == "fork":
|
|
126
|
+
return args.func # Fork derives native engine from the original source.
|
|
127
|
+
if args.cmd == "steer" and codex and args.timeout is not None:
|
|
128
|
+
refuse("--timeout on steer is Claude-only")
|
|
129
|
+
if args.cmd == "reconcile" and not codex and args.turn_id is not None:
|
|
130
|
+
refuse("--turn-id is Codex-only")
|
|
131
|
+
if args.cmd == "wait":
|
|
132
|
+
if codex and (args.next_turn or args.after_turn is not None or args.known_input is not None):
|
|
133
|
+
refuse("--next-turn/--after-turn/--known-input are Claude-only")
|
|
134
|
+
if not codex and (settle or args.wake_grace is not None):
|
|
135
|
+
refuse("--settle/--wake-grace are Codex-only")
|
|
136
|
+
if codex and not settle and args.wake_grace is not None:
|
|
137
|
+
refuse("--wake-grace requires --settle")
|
|
138
|
+
if codex:
|
|
139
|
+
from ccx.codex.settle import DEFAULT_WAKE_GRACE_S
|
|
140
|
+
args.wake_grace = (DEFAULT_WAKE_GRACE_S if args.wake_grace is None
|
|
141
|
+
else args.wake_grace)
|
|
142
|
+
if args.cmd == "notify":
|
|
143
|
+
if codex and args.next_turn:
|
|
144
|
+
refuse("--next-turn is Claude-only")
|
|
145
|
+
if not codex and settle:
|
|
146
|
+
refuse("--settle is Codex-only")
|
|
147
|
+
if args.cmd in {"approve", "reject"}:
|
|
148
|
+
if codex and (args.wait or args.timeout is not None):
|
|
149
|
+
refuse("--wait/--timeout on a native Codex gate are unsupported; use ccx wait")
|
|
150
|
+
if not codex and settle:
|
|
151
|
+
refuse("--settle is Codex-only")
|
|
152
|
+
if codex and args.cmd == "reject" and args.reason is not None:
|
|
153
|
+
refuse("--reason is Claude-only")
|
|
154
|
+
if args.cmd == "set" and codex and (args.permissions is not None or args.restart):
|
|
155
|
+
refuse("--permissions/--restart are Claude-only")
|
|
156
|
+
if args.cmd == "compact" and codex and args.operation_id is not None:
|
|
157
|
+
refuse("--operation-id is Claude-only")
|
|
158
|
+
if args.cmd == "logs" and codex and (args.format != "conversation" or args.jsonl):
|
|
159
|
+
refuse("--format rendered/--jsonl are Claude-only")
|
|
160
|
+
codex_handlers = {
|
|
161
|
+
"send": codex_commands.cmd_send,
|
|
162
|
+
"steer": codex_commands.cmd_steer,
|
|
163
|
+
"reconcile": codex_commands.cmd_reconcile,
|
|
164
|
+
"wait": codex_commands.cmd_wait,
|
|
165
|
+
"notify": codex_commands.cmd_notify,
|
|
166
|
+
"approve": codex_commands.cmd_approve,
|
|
167
|
+
"reject": codex_commands.cmd_reject,
|
|
168
|
+
"set": codex_commands.cmd_set,
|
|
169
|
+
"compact": codex_commands.cmd_compact,
|
|
170
|
+
}
|
|
171
|
+
return codex_handlers[args.cmd] if codex and args.cmd in codex_handlers else args.func
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _deepest_parser(parser, argv):
|
|
175
|
+
"""The (sub)command parser the argv selects, so errors show its usage."""
|
|
176
|
+
import argparse
|
|
177
|
+
for token in argv:
|
|
178
|
+
actions = [a for a in parser._actions if isinstance(a, argparse._SubParsersAction)]
|
|
179
|
+
if actions and token in actions[0].choices:
|
|
180
|
+
parser = actions[0].choices[token]
|
|
181
|
+
return parser
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def main(argv=None):
|
|
185
|
+
with quiet_broken_stdout():
|
|
186
|
+
_main(argv)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# Internal relays run inside workers CCX launched; they must keep working there.
|
|
190
|
+
_SANDBOX_EXEMPT = {"skills", "host", "statusline-relay", "hooks-relay", "_live-vars-exec"}
|
|
191
|
+
|
|
192
|
+
# Explicit top-level user commands. Internal relays and the controller's
|
|
193
|
+
# private detached-host verbs keep a strict existing-store requirement.
|
|
194
|
+
_AUTO_INIT_COMMANDS = frozenset({
|
|
195
|
+
"new", "import", "fork", "resume", "send", "steer", "reconcile",
|
|
196
|
+
"wait", "notify", "answer", "approve", "reject", "respond",
|
|
197
|
+
"set", "compact", "stop", "kill", "attach", "shutdown", "untrust",
|
|
198
|
+
"vars", "list", "logs", "report", "context", "peek", "events",
|
|
199
|
+
"usage", "controller",
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _refuse_codex_sandbox(cmd: str) -> None:
|
|
204
|
+
"""Fail once, clearly, when a Codex parent runs ccx inside its own sandbox.
|
|
205
|
+
|
|
206
|
+
CCX starts native Claude/Codex processes and uses ~/.claude, ~/.codex and
|
|
207
|
+
its root, so inside the sandbox it would fail later in scattered ways.
|
|
208
|
+
Codex sets CODEX_SANDBOX only on sandboxed commands (seatbelt on macOS).
|
|
209
|
+
CODEX_SANDBOX_NETWORK_DISABLED is no signal: Codex also sets it on commands
|
|
210
|
+
the user approved to run outside the sandbox.
|
|
211
|
+
"""
|
|
212
|
+
if not os.environ.get("CODEX_SANDBOX") or cmd in _SANDBOX_EXEMPT:
|
|
213
|
+
return
|
|
214
|
+
sys.stderr.write(
|
|
215
|
+
"ccx unavailable: running inside the Codex sandbox (CODEX_SANDBOX is set). "
|
|
216
|
+
"CCX starts Claude/Codex processes and writes outside the workspace, which the "
|
|
217
|
+
"sandbox blocks. Rerun this ccx command with escalated permissions (outside the "
|
|
218
|
+
"sandbox), or start Codex with full access.\n"
|
|
219
|
+
)
|
|
220
|
+
raise SystemExit(3)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _main(argv=None):
|
|
224
|
+
raw_argv = list(sys.argv[1:] if argv is None else argv)
|
|
225
|
+
# Answer before touching state so `ccx --version` never creates ~/.ccx.
|
|
226
|
+
if raw_argv in (["--version"], ["-V"]):
|
|
227
|
+
print(f"ccx {ccx_version()}")
|
|
228
|
+
return
|
|
229
|
+
from ccx.errors import SessionUnavailable
|
|
230
|
+
try:
|
|
231
|
+
reject_codex_blocking_call(raw_argv)
|
|
232
|
+
except SessionUnavailable as exc:
|
|
233
|
+
sys.stderr.write(
|
|
234
|
+
"ccx unavailable: caller worker identity is not proven in this "
|
|
235
|
+
f"CCX_ROOT; use the worker's original root: {exc}\n"
|
|
236
|
+
)
|
|
237
|
+
raise SystemExit(3) from None
|
|
238
|
+
if raw_argv[:1] == ["help"]:
|
|
239
|
+
raw_argv = raw_argv[1:] + ["--help"] # `ccx help [command]`
|
|
240
|
+
p = build_parser()
|
|
241
|
+
# Parse first: --help and usage errors exit here without creating ~/.ccx.
|
|
242
|
+
args, extra = p.parse_known_args(raw_argv)
|
|
243
|
+
if extra:
|
|
244
|
+
_deepest_parser(p, raw_argv).error(f"unrecognized arguments: {' '.join(extra)}")
|
|
245
|
+
_refuse_codex_sandbox(args.cmd)
|
|
246
|
+
if args.cmd not in _SANDBOX_EXEMPT:
|
|
247
|
+
# Keep `ccx skills install` copies current after an upgrade; cheap
|
|
248
|
+
# when nothing changed, and never fatal.
|
|
249
|
+
from ccx.cli.skills import refresh_installed
|
|
250
|
+
refresh_installed()
|
|
251
|
+
if args.cmd == "init":
|
|
252
|
+
# Fully wired explicit empty-root bootstrap, not a legacy migration.
|
|
253
|
+
args.func(args)
|
|
254
|
+
return
|
|
255
|
+
if args.cmd == "skills":
|
|
256
|
+
# Setup discovery must work before any root exists or is initialized.
|
|
257
|
+
args.func(args)
|
|
258
|
+
return
|
|
259
|
+
if args.cmd == "rpc":
|
|
260
|
+
# RPC validates capabilities before opening an active store.
|
|
261
|
+
args.func(args)
|
|
262
|
+
return
|
|
263
|
+
# A worker-owned Python shim is pinned to its creator root, even when
|
|
264
|
+
# the command has an unrelated CCX_ROOT in its environment. The native
|
|
265
|
+
# Python child still inherits that caller environment unchanged.
|
|
266
|
+
if args.cmd == "_live-vars-exec" and args.root:
|
|
267
|
+
# Shell functions generated before --root existed still call without
|
|
268
|
+
# it; they keep resolving through the caller's CCX_ROOT.
|
|
269
|
+
from ccx import constants
|
|
270
|
+
# Native Python remains the caller's program: save its exact environment
|
|
271
|
+
# before pinning CCX internals to this worker's (different) root.
|
|
272
|
+
args._bridge_native_env = os.environ.copy()
|
|
273
|
+
constants.select_root(args.root)
|
|
274
|
+
# Validate the sealed store before handlers perform side effects.
|
|
275
|
+
from ccx.constants import CCX_ROOT
|
|
276
|
+
from ccx.store.root import GenerationError, require_ccx_active, open_or_initialize
|
|
277
|
+
try:
|
|
278
|
+
user_facing = (args.cmd in _AUTO_INIT_COMMANDS
|
|
279
|
+
and not (args.cmd == "controller"
|
|
280
|
+
and args.controller_cmd in {"_launch", "_host"}))
|
|
281
|
+
if user_facing:
|
|
282
|
+
active_store, created = open_or_initialize(CCX_ROOT)
|
|
283
|
+
if created:
|
|
284
|
+
sys.stderr.write(f"ccx: created store at {active_store.root}\n")
|
|
285
|
+
else:
|
|
286
|
+
active_store = require_ccx_active(CCX_ROOT)
|
|
287
|
+
except (GenerationError, OSError) as exc:
|
|
288
|
+
sys.stderr.write(f"ccx unavailable: {exc}\n")
|
|
289
|
+
raise SystemExit(3) from None
|
|
290
|
+
from ccx.errors import CcxError, SessionNotFound
|
|
291
|
+
try:
|
|
292
|
+
_preflight_worker_references(args, active_store)
|
|
293
|
+
except CcxError as exc:
|
|
294
|
+
sys.stderr.write(
|
|
295
|
+
f"{exc}\n" if isinstance(exc, SessionNotFound)
|
|
296
|
+
else f"ccx worker discovery: {exc}\n"
|
|
297
|
+
)
|
|
298
|
+
raise SystemExit(exc.exit_code) from exc
|
|
299
|
+
handler = _native_handler(args, active_store)
|
|
300
|
+
handler(args)
|
|
301
|
+
return
|