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
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import os
|
|
5
|
+
import threading
|
|
6
|
+
from typing import Any
|
|
7
|
+
from ccx.codex.protocol import ProtocolError, ProtocolTransportError, error_payload
|
|
8
|
+
from ccx.codex.records import now_ms
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_LAUNCH_SETTLE_CAP_S = 10.0
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _native_history_budget(message: dict[str, Any]) -> float:
|
|
15
|
+
"""One caller-provided native read budget, not a fixed daemon timeout."""
|
|
16
|
+
budget = message.get("observeSeconds", 30.0)
|
|
17
|
+
if (type(budget) not in (int, float)
|
|
18
|
+
or not math.isfinite(budget) or budget <= 0):
|
|
19
|
+
raise ValueError("native history requires positive observation seconds")
|
|
20
|
+
return float(budget)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def linger_seconds() -> float:
|
|
24
|
+
try:
|
|
25
|
+
return float(os.environ.get("CCX_CODEXD_LINGER", "900"))
|
|
26
|
+
except ValueError:
|
|
27
|
+
return 900.0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _bounded(value: Any, limit: int = 1000) -> Any:
|
|
31
|
+
if not isinstance(value, str) or len(value) <= limit:
|
|
32
|
+
return value
|
|
33
|
+
return value[: limit - 1] + "…"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _compact_native_refusal(error: ProtocolError) -> bool:
|
|
37
|
+
if isinstance(error, ProtocolTransportError) or error.request_id is None:
|
|
38
|
+
return False
|
|
39
|
+
data = error.data if isinstance(error.data, dict) else {}
|
|
40
|
+
info = data.get("codexErrorInfo", data)
|
|
41
|
+
kind = info.get("activeTurnNotSteerable") if isinstance(info, dict) else None
|
|
42
|
+
if isinstance(kind, dict):
|
|
43
|
+
return kind.get("turnKind") == "compact"
|
|
44
|
+
# Codex 0.159.3 preserves structured error data on steer, but turn/start
|
|
45
|
+
# wraps the same native refusal as an internal-error string without data.
|
|
46
|
+
# Match only the observed method/code/literal; never all -32603 failures.
|
|
47
|
+
if error.method == "turn/start" and error.code == -32603:
|
|
48
|
+
return error.message == "failed to submit turn input: ActiveTurnNotSteerable { turn_kind: Compact }"
|
|
49
|
+
return error.message == "cannot steer a compact turn"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _protocol_failure(error: ProtocolError) -> dict[str, Any]:
|
|
53
|
+
"""One daemon-global failure envelope.
|
|
54
|
+
|
|
55
|
+
``error`` stays the human-readable string every existing caller reads;
|
|
56
|
+
``protocolError`` carries the machine-readable fields that would otherwise
|
|
57
|
+
be flattened away at this process boundary.
|
|
58
|
+
"""
|
|
59
|
+
return {
|
|
60
|
+
"ok": False,
|
|
61
|
+
"error": str(error),
|
|
62
|
+
"protocolError": error_payload(error),
|
|
63
|
+
**({"reason": "compaction-busy", "retryable": True}
|
|
64
|
+
if _compact_native_refusal(error) else {}),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _abandon_active_compact(record: dict[str, Any], reason: str) -> bool:
|
|
69
|
+
"""Close a persisted compact that can no longer yield native proof.
|
|
70
|
+
|
|
71
|
+
The compact count does not advance: only native compaction evidence proves
|
|
72
|
+
success. Recording the exact operation as unknown prevents a daemon
|
|
73
|
+
restart/kill from poisoning a later generic ``notify`` (or a compact status
|
|
74
|
+
read) with a stale active marker.
|
|
75
|
+
"""
|
|
76
|
+
active = record.get("activeCompact")
|
|
77
|
+
if not isinstance(active, dict):
|
|
78
|
+
return False
|
|
79
|
+
record["lastCompact"] = {
|
|
80
|
+
"operationId": active.get("operationId"),
|
|
81
|
+
"baselineCompactCount": active.get("baselineCompactCount"),
|
|
82
|
+
"targetCompactCount": active.get("targetCompactCount"),
|
|
83
|
+
"startedAtMs": active.get("startedAtMs"),
|
|
84
|
+
"completedAtMs": now_ms(),
|
|
85
|
+
"contextBefore": active.get("contextBefore"),
|
|
86
|
+
"turnId": active.get("turnId"),
|
|
87
|
+
"contextItemId": active.get("contextItemId"),
|
|
88
|
+
"nativeTurnStarted": active.get("nativeTurnStarted") is True,
|
|
89
|
+
"nativeTurnSettled": False,
|
|
90
|
+
"status": "unknown",
|
|
91
|
+
"reason": reason,
|
|
92
|
+
}
|
|
93
|
+
# Original fullUUID effects live exclusively in GenerationStore receipts.
|
|
94
|
+
# Do not create or update a second short-keyed compact receipt.
|
|
95
|
+
record["activeCompact"] = None
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
def _retire_runtime_markers(record: dict[str, Any], reason: str) -> bool:
|
|
99
|
+
"""Drop markers only a vanished app-server connection could resolve."""
|
|
100
|
+
changed = _abandon_active_compact(record, reason)
|
|
101
|
+
if record.get("nativeTurnStart") is not None:
|
|
102
|
+
record["nativeTurnStart"] = None
|
|
103
|
+
changed = True
|
|
104
|
+
return changed
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class _TurnLaunch:
|
|
108
|
+
"""A ``turn/start`` that is irrevocably in flight.
|
|
109
|
+
|
|
110
|
+
Published under the worker lock in the same critical section that checks the
|
|
111
|
+
cancellation marker, so a concurrent ``kill`` either wins (the loader never
|
|
112
|
+
starts the turn) or sees this and takes responsibility for cancelling it.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
__slots__ = ("done", "turn_id")
|
|
116
|
+
|
|
117
|
+
def __init__(self) -> None:
|
|
118
|
+
self.done = threading.Event()
|
|
119
|
+
self.turn_id: str | None = None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ReleasePending(ProtocolError):
|
|
123
|
+
"""Codex has not answered an earlier `thread/unsubscribe` of this thread."""
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class _ThreadRelease:
|
|
127
|
+
"""One `thread/unsubscribe`, fenced from teardown until Codex answers it."""
|
|
128
|
+
|
|
129
|
+
def __init__(self, thread_id: str):
|
|
130
|
+
self.thread_id = thread_id
|
|
131
|
+
self.settled = threading.Event()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class WorkerKilled(Exception):
|
|
135
|
+
"""A load was abandoned because the worker was killed while it ran.
|
|
136
|
+
|
|
137
|
+
Internal control flow, never surfaced to a caller as-is: the loader
|
|
138
|
+
turns it into an ordinary refused-load reply. It is deliberately not a
|
|
139
|
+
`ProtocolError` — nothing went wrong with app-server.
|
|
140
|
+
"""
|