ccx-native 0.0.0-stage → 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 -2
- package/bin/ccx +56 -0
- 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 +40 -4
- 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
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Read-only presence of the runtime that writes a worker's event journal.
|
|
2
|
+
|
|
3
|
+
A runtime killed abruptly (SIGKILL, OOM) writes no final event, so a held
|
|
4
|
+
event read alone cannot tell a quiet worker from a dead one. Each runtime
|
|
5
|
+
already holds a kernel lifetime lease for its whole life: a Claude host
|
|
6
|
+
``runtime-metadata/host-locks/<worker>.lease.lock`` (``host._host_process_lease``)
|
|
7
|
+
and codexd ``runtime-metadata/pid/codexd.lease.lock``
|
|
8
|
+
(``transport.daemon_process_lease``). The kernel drops it with the process.
|
|
9
|
+
|
|
10
|
+
This module only *looks* at those leases, through the kernel lock table
|
|
11
|
+
(``oslayer.held_flock_inodes``: ``/proc/locks`` on Linux; Darwin has none, so
|
|
12
|
+
``runtime`` is always ``unknown`` there). It never
|
|
13
|
+
takes a lock: even a momentary non-blocking probe could make a starting
|
|
14
|
+
Claude host, whose lease acquisition is non-blocking, refuse to start. It
|
|
15
|
+
writes nothing, calls no runtime and starts nothing.
|
|
16
|
+
|
|
17
|
+
``runtime`` values:
|
|
18
|
+
|
|
19
|
+
- ``live``: the lease of this root/generation/worker runtime is held.
|
|
20
|
+
- ``gone``: the lease has been free, and the recorded runtime process is not
|
|
21
|
+
alive, for a whole startup grace (the one ``worker.wait`` uses: 0.35 s, or
|
|
22
|
+
5 s while booting or resuming) within this observation. It prompts an
|
|
23
|
+
authoritative bounded ``worker.wait``; it never proves that a turn ended.
|
|
24
|
+
- ``unknown``: anything else (no kernel lock table, an untrusted lease file,
|
|
25
|
+
a free lease whose recorded process is still alive, or a free lease not yet
|
|
26
|
+
observed for the grace).
|
|
27
|
+
"""
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import os
|
|
31
|
+
import stat
|
|
32
|
+
import time
|
|
33
|
+
from typing import Callable
|
|
34
|
+
|
|
35
|
+
from ccx.system import oslayer
|
|
36
|
+
from ccx.store.liveness import owned_process
|
|
37
|
+
|
|
38
|
+
LIVE = "live"
|
|
39
|
+
GONE = "gone"
|
|
40
|
+
UNKNOWN = "unknown"
|
|
41
|
+
|
|
42
|
+
HOST_MISSING_GRACE_S = 0.35
|
|
43
|
+
BOOTING_GRACE_S = 5.0
|
|
44
|
+
_BOOTING = frozenset({"booting", "resuming", "starting"})
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _lease_file(store, engine: str, worker_id: str) -> os.stat_result | None | bool:
|
|
48
|
+
"""The lease file's stat, None when it was never created, False if untrusted."""
|
|
49
|
+
parts, name = ((("runtime-metadata", "host-locks"), worker_id + ".lease.lock")
|
|
50
|
+
if engine == "claude" else
|
|
51
|
+
(("runtime-metadata", "pid"), "codexd.lease.lock"))
|
|
52
|
+
try:
|
|
53
|
+
with store._gen(*parts) as directory:
|
|
54
|
+
info = os.stat(name, dir_fd=directory, follow_symlinks=False)
|
|
55
|
+
except FileNotFoundError:
|
|
56
|
+
return None
|
|
57
|
+
if (not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid()
|
|
58
|
+
or info.st_mode & 0o077):
|
|
59
|
+
return False
|
|
60
|
+
return info
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _codexd_identity(store) -> tuple[object, object]:
|
|
64
|
+
"""The recorded codexd pid/start of this exact root and generation."""
|
|
65
|
+
from ccx.store.root import _read_json
|
|
66
|
+
try:
|
|
67
|
+
with store._gen("runtime-metadata", "pid") as directory:
|
|
68
|
+
meta = _read_json(directory, "codexd.json")
|
|
69
|
+
except (FileNotFoundError, OSError, ValueError):
|
|
70
|
+
return None, None
|
|
71
|
+
if (not isinstance(meta, dict) or meta.get("generation") != store.generation
|
|
72
|
+
or meta.get("rootRealpath") != str(store.root)):
|
|
73
|
+
return None, None
|
|
74
|
+
return meta.get("pid"), meta.get("startToken")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def lease(store, engine: str, worker_id: str, record: dict | None) -> str:
|
|
78
|
+
"""One sample: ``held``, ``free`` (and the recorded process is not alive) or ``unknown``."""
|
|
79
|
+
# Without a kernel lock table nothing can be observed, not even a free
|
|
80
|
+
# lease: a never-created lease file must not become "gone" here.
|
|
81
|
+
held = oslayer.held_flock_inodes()
|
|
82
|
+
if held is None:
|
|
83
|
+
return UNKNOWN
|
|
84
|
+
info = _lease_file(store, engine, worker_id)
|
|
85
|
+
if info is False:
|
|
86
|
+
return UNKNOWN
|
|
87
|
+
if (info is not None and
|
|
88
|
+
(os.major(info.st_dev), os.minor(info.st_dev), info.st_ino) in held):
|
|
89
|
+
return "held"
|
|
90
|
+
if engine == "claude":
|
|
91
|
+
lifecycle = (record or {}).get("lifecycle") or {}
|
|
92
|
+
pid, start = lifecycle.get("hostPid"), lifecycle.get("hostStart")
|
|
93
|
+
else:
|
|
94
|
+
pid, start = _codexd_identity(store)
|
|
95
|
+
# A free lease next to a recorded process that is still alive is a start
|
|
96
|
+
# before its lease, or a lease this reader cannot see: never "gone".
|
|
97
|
+
if pid is not None and owned_process(pid, start) != "dead":
|
|
98
|
+
return UNKNOWN
|
|
99
|
+
return "free"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class RuntimeObserver:
|
|
103
|
+
"""Presence across one held read; no state outlives it or is shared."""
|
|
104
|
+
|
|
105
|
+
def __init__(self, store, target: str,
|
|
106
|
+
clock: Callable[[], float] = time.monotonic):
|
|
107
|
+
owner = store.resolve(target)
|
|
108
|
+
self._store, self._target = store, target
|
|
109
|
+
self._engine, self._worker_id = owner.engine, owner.worker_id
|
|
110
|
+
self._clock = clock
|
|
111
|
+
self._free_since: float | None = None
|
|
112
|
+
self.value: str = UNKNOWN
|
|
113
|
+
self.seen_live = False
|
|
114
|
+
|
|
115
|
+
def sample(self) -> str:
|
|
116
|
+
# The shared activation lease, as every other source read takes it.
|
|
117
|
+
with self._store.lease():
|
|
118
|
+
record = self._store.record(self._target)
|
|
119
|
+
observed = lease(self._store, self._engine, self._worker_id, record)
|
|
120
|
+
now = self._clock()
|
|
121
|
+
if observed == "held":
|
|
122
|
+
self._free_since = None
|
|
123
|
+
self.seen_live = True
|
|
124
|
+
self.value = LIVE
|
|
125
|
+
elif observed == "free":
|
|
126
|
+
if self._free_since is None:
|
|
127
|
+
self._free_since = now
|
|
128
|
+
native = ((record or {}).get("lifecycle") or {}).get("state")
|
|
129
|
+
grace = BOOTING_GRACE_S if native in _BOOTING else HOST_MISSING_GRACE_S
|
|
130
|
+
self.value = GONE if now - self._free_since >= grace else UNKNOWN
|
|
131
|
+
else:
|
|
132
|
+
self._free_since = None
|
|
133
|
+
self.value = UNKNOWN
|
|
134
|
+
return self.value
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
__all__ = ["GONE", "LIVE", "UNKNOWN", "RuntimeObserver", "lease"]
|