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,295 @@
|
|
|
1
|
+
"""Machine-level maintenance: stop CCX runtimes, undo CCX's trust writes.
|
|
2
|
+
|
|
3
|
+
Both commands act on what CCX itself owns and nothing else: global ``shutdown``
|
|
4
|
+
stops managed Claude hosts, Codex workers loaded in the shared codexd (then
|
|
5
|
+
codexd) and the parent-notification supervisor. An engine-scoped shutdown
|
|
6
|
+
leaves the other engine and the notification supervisor untouched. ``untrust``
|
|
7
|
+
reverts only the ``~/.claude.json`` trust entries recorded in CCX's ledger. External
|
|
8
|
+
Claude sessions (native background agents, interactive terminals) and running
|
|
9
|
+
controllers are never touched.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import signal
|
|
17
|
+
import sys
|
|
18
|
+
import time
|
|
19
|
+
|
|
20
|
+
from ccx.constants import EXIT_UNAVAILABLE
|
|
21
|
+
from ccx.errors import CcxError
|
|
22
|
+
from ccx.ops.terminate import KILL_TIMEOUT_DEFAULT_S
|
|
23
|
+
|
|
24
|
+
_NOTIFYD_STOP_S = 5.0
|
|
25
|
+
_CODEXD_EXIT_S = 10.0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# --- shutdown ----------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
def _live_claude_workers(store) -> tuple[list[dict], list[dict]]:
|
|
31
|
+
from ccx.store.liveness import owned_process
|
|
32
|
+
from ccx.store.root import GenerationError, StorageIntegrityError
|
|
33
|
+
from ccx.store.identity import IdentityError
|
|
34
|
+
|
|
35
|
+
rows, errors = [], []
|
|
36
|
+
# The ACTIVE generation's full-ID owners, never legacy sessions/shorts.
|
|
37
|
+
for owner_row in store.list_workers(engine="claude"):
|
|
38
|
+
row = {key: owner_row[key] for key in (
|
|
39
|
+
"engine", "worker", "worker_id", "short", "name", "state")}
|
|
40
|
+
try:
|
|
41
|
+
record = store.record(row["worker"])
|
|
42
|
+
lifecycle = record.get("lifecycle") or {}
|
|
43
|
+
if not isinstance(lifecycle, dict):
|
|
44
|
+
raise StorageIntegrityError("original Claude lifecycle is malformed")
|
|
45
|
+
# Tokenless/unprovable PIDs remain visible so kill must refuse
|
|
46
|
+
# rather than letting shutdown mistake uncertainty for absence.
|
|
47
|
+
def present(pid, start):
|
|
48
|
+
return owned_process(pid, start) != "dead"
|
|
49
|
+
|
|
50
|
+
if present(lifecycle.get("hostPid"), lifecycle.get("hostStart")) or present(
|
|
51
|
+
lifecycle.get("workerPid"), lifecycle.get("workerStart")
|
|
52
|
+
):
|
|
53
|
+
rows.append(row)
|
|
54
|
+
except (GenerationError, IdentityError, OSError) as error:
|
|
55
|
+
row["action"] = "failed"
|
|
56
|
+
rows.append(row)
|
|
57
|
+
errors.append({"engine": "claude", "worker": row["worker"],
|
|
58
|
+
"short": row["short"], "error": str(error)})
|
|
59
|
+
return rows, errors
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _loaded_codex_workers(store) -> tuple[str, list[dict], list[dict], int | None]:
|
|
63
|
+
from ccx.codex.runtime import observe_registry
|
|
64
|
+
from ccx.constants import CODEXD_RPC_GENERATION
|
|
65
|
+
from ccx.store.root import GenerationError
|
|
66
|
+
from ccx.store.identity import IdentityError
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
registry = observe_registry(timeout=2.0)
|
|
70
|
+
except (CcxError, OSError, ValueError) as error:
|
|
71
|
+
registry = {"presence": "inconclusive", "reply": {"error": str(error)}}
|
|
72
|
+
presence = registry.get("presence")
|
|
73
|
+
if presence == "absent":
|
|
74
|
+
return presence, [], [], None
|
|
75
|
+
|
|
76
|
+
rows, errors = [], []
|
|
77
|
+
raw = registry.get("reply")
|
|
78
|
+
# A positively served response from an authenticated daemon of another
|
|
79
|
+
# RPC generation is not a readable worker inventory. Only its generation
|
|
80
|
+
# and the generation-agnostic daemon-stop operation can be used.
|
|
81
|
+
version = raw.get("rpcGeneration") if isinstance(raw, dict) else None
|
|
82
|
+
if (isinstance(raw, dict) and raw.get("transport") == "served"
|
|
83
|
+
and raw.get("ok") is True and type(version) is int
|
|
84
|
+
and version != CODEXD_RPC_GENERATION):
|
|
85
|
+
return "older-generation", rows, errors, version
|
|
86
|
+
workers = raw.get("workers") if isinstance(raw, dict) else None
|
|
87
|
+
if presence != "served" or not isinstance(workers, dict):
|
|
88
|
+
reason = raw.get("error") if isinstance(raw, dict) else presence
|
|
89
|
+
errors.append({"engine": "codex", "worker": "codexd", "short": "codexd",
|
|
90
|
+
"error": f"codexd original registry unavailable or malformed: {reason}"})
|
|
91
|
+
return "inconclusive", rows, errors, None
|
|
92
|
+
for worker_id, state in sorted(workers.items()):
|
|
93
|
+
handle = f"codex:{worker_id}"
|
|
94
|
+
try:
|
|
95
|
+
owner = store.resolve(handle)
|
|
96
|
+
record = store.record(owner.worker)
|
|
97
|
+
if not isinstance(state, str) or not state:
|
|
98
|
+
raise ValueError("original Codex worker registry state malformed")
|
|
99
|
+
rows.append({"engine": "codex", "worker": owner.worker,
|
|
100
|
+
"worker_id": owner.worker_id, "short": owner.short,
|
|
101
|
+
"name": record.get("name"), "state": state})
|
|
102
|
+
except (GenerationError, IdentityError, OSError, ValueError) as error:
|
|
103
|
+
errors.append({"engine": "codex", "worker": handle,
|
|
104
|
+
"short": str(worker_id), "error": str(error)})
|
|
105
|
+
return presence, rows, errors, None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _codexd_generation_note(version: int) -> str:
|
|
109
|
+
from ccx.constants import CODEXD_RPC_GENERATION
|
|
110
|
+
direction = "older" if version < CODEXD_RPC_GENERATION else "newer"
|
|
111
|
+
return f"{direction} generation {version}"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _stop_codexd(deadline: float | None, *, older_generation: int | None = None) -> str:
|
|
115
|
+
from ccx.codex.runtime import daemon_generation, daemon_stop, observe_registry
|
|
116
|
+
from ccx.constants import CODEXD_RPC_GENERATION
|
|
117
|
+
|
|
118
|
+
# Older registry layouts cannot be enumerated/unloaded individually:
|
|
119
|
+
# daemon teardown is the one cross-generation recovery operation. It
|
|
120
|
+
# unloads its own workers as it exits and retains their native histories.
|
|
121
|
+
reply = daemon_stop(force=older_generation is not None)
|
|
122
|
+
if not reply.get("ok"):
|
|
123
|
+
if reply.get("transport") == "absent":
|
|
124
|
+
return "not-running"
|
|
125
|
+
# Workers this shutdown did not kill are still active: ones a codexd
|
|
126
|
+
# from another ccx version holds (its registry is unreadable here, so
|
|
127
|
+
# they were never listed) or ones loaded after the kills.
|
|
128
|
+
why = ("it is a codexd from another ccx version, whose workers this "
|
|
129
|
+
"ccx cannot list or kill"
|
|
130
|
+
if daemon_generation(reply) != CODEXD_RPC_GENERATION
|
|
131
|
+
else "workers were loaded after the kills")
|
|
132
|
+
raise CcxError(
|
|
133
|
+
f"codexd stop rejected: {reply.get('error') or reply} ({why}); "
|
|
134
|
+
"codexd remains running to protect those workers; resolve the "
|
|
135
|
+
"loaded-worker ownership before retrying `ccx shutdown --engine codex`")
|
|
136
|
+
wait_until = time.monotonic() + _CODEXD_EXIT_S
|
|
137
|
+
if deadline is not None:
|
|
138
|
+
wait_until = min(wait_until, deadline)
|
|
139
|
+
while time.monotonic() < wait_until:
|
|
140
|
+
if observe_registry(timeout=0.5)["presence"] == "absent":
|
|
141
|
+
return (f"stopped ({_codexd_generation_note(older_generation)})"
|
|
142
|
+
if older_generation is not None else "stopped")
|
|
143
|
+
time.sleep(0.1)
|
|
144
|
+
raise CcxError("codexd accepted stop but is still serving")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _bound_notifyd_pid(store) -> tuple[int, str] | None:
|
|
148
|
+
"""Only a positively owned, current-generation notifier can be stopped.
|
|
149
|
+
|
|
150
|
+
A missing/malformed stamp denotes a daemon not yet serving. A proven
|
|
151
|
+
dead original is stale metadata, not a shutdown blocker; uncertain
|
|
152
|
+
PID/start observations must never authorize signalling.
|
|
153
|
+
"""
|
|
154
|
+
from ccx.constants import NOTIFYD_GENERATION
|
|
155
|
+
from ccx.store.liveness import owned_process
|
|
156
|
+
from ccx.notify.daemon import _read_notifyd_stamp
|
|
157
|
+
|
|
158
|
+
stamp = _read_notifyd_stamp()
|
|
159
|
+
if stamp is None:
|
|
160
|
+
return None # no published serving identity
|
|
161
|
+
pid, version, generation, start = stamp
|
|
162
|
+
presence = owned_process(pid, start)
|
|
163
|
+
if presence == "dead":
|
|
164
|
+
return None
|
|
165
|
+
if presence != "alive":
|
|
166
|
+
raise CcxError("notification supervisor PID/start unprovable")
|
|
167
|
+
if version != NOTIFYD_GENERATION or generation != store.generation:
|
|
168
|
+
raise CcxError("notification supervisor version/ACTIVE generation incompatible")
|
|
169
|
+
return pid, start
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _stop_notifyd(bound: tuple[int, str] | None) -> str:
|
|
173
|
+
from ccx.constants import CCX_ROOT
|
|
174
|
+
from ccx.store.root import require_ccx_active
|
|
175
|
+
from ccx.store.liveness import owned_process
|
|
176
|
+
|
|
177
|
+
if bound is None:
|
|
178
|
+
return "not-running"
|
|
179
|
+
pid, start = bound
|
|
180
|
+
# Reprove the exact four-field stamp and PID/start once, at the signal.
|
|
181
|
+
current = _bound_notifyd_pid(require_ccx_active(CCX_ROOT))
|
|
182
|
+
if current is None:
|
|
183
|
+
return "not-running"
|
|
184
|
+
if current != bound:
|
|
185
|
+
raise CcxError("notification supervisor identity changed before SIGTERM")
|
|
186
|
+
try:
|
|
187
|
+
os.kill(pid, signal.SIGTERM)
|
|
188
|
+
except ProcessLookupError:
|
|
189
|
+
return "not-running"
|
|
190
|
+
except OSError as error:
|
|
191
|
+
raise CcxError(f"cannot stop notification supervisor {pid}: {error}") from error
|
|
192
|
+
wait_until = time.monotonic() + _NOTIFYD_STOP_S
|
|
193
|
+
while time.monotonic() < wait_until:
|
|
194
|
+
presence = owned_process(pid, start)
|
|
195
|
+
if presence == "dead":
|
|
196
|
+
return "stopped"
|
|
197
|
+
if presence == "unprovable":
|
|
198
|
+
raise CcxError(f"notification supervisor {pid} exit unprovable")
|
|
199
|
+
time.sleep(0.05)
|
|
200
|
+
raise CcxError(f"notification supervisor {pid} did not stop")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def shutdown(*, dry_run: bool = False, timeout: float | None = KILL_TIMEOUT_DEFAULT_S,
|
|
204
|
+
engine: str | None = None) -> dict:
|
|
205
|
+
"""Confirmed CCX-owned shutdown, optionally engine-scoped; timeout per kill."""
|
|
206
|
+
from ccx.constants import CCX_ROOT
|
|
207
|
+
from ccx.store.root import GenerationError, require_ccx_active
|
|
208
|
+
from ccx.store.identity import IdentityError
|
|
209
|
+
|
|
210
|
+
if engine not in (None, "claude", "codex"):
|
|
211
|
+
raise ValueError(f"unsupported shutdown engine: {engine}")
|
|
212
|
+
|
|
213
|
+
errors: list[dict] = []
|
|
214
|
+
claude_rows, codex_rows = [], []
|
|
215
|
+
try:
|
|
216
|
+
store = require_ccx_active(CCX_ROOT)
|
|
217
|
+
if engine != "codex":
|
|
218
|
+
claude_rows, claude_errors = _live_claude_workers(store)
|
|
219
|
+
errors.extend(claude_errors)
|
|
220
|
+
except (GenerationError, IdentityError, OSError) as error:
|
|
221
|
+
# An unreadable/non-ACTIVE global owner index never means an empty
|
|
222
|
+
# machine. No runtime control is safe before it is readable.
|
|
223
|
+
return {"dry_run": dry_run, "claude": [], "codex": [],
|
|
224
|
+
"codexd": "untouched" if engine == "claude" else "failed",
|
|
225
|
+
"notifyd": "failed" if engine is None else "untouched",
|
|
226
|
+
"errors": [{"engine": None, "worker": "inventory",
|
|
227
|
+
"short": "inventory", "error": str(error)}], "ok": False}
|
|
228
|
+
codexd = "untouched"
|
|
229
|
+
older_generation = None
|
|
230
|
+
if engine != "claude":
|
|
231
|
+
presence, codex_rows, codex_errors, older_generation = _loaded_codex_workers(store)
|
|
232
|
+
errors.extend(codex_errors)
|
|
233
|
+
codexd = ("not-running" if presence == "absent" else
|
|
234
|
+
"failed" if codex_errors else
|
|
235
|
+
f"would-stop ({_codexd_generation_note(older_generation)})"
|
|
236
|
+
if older_generation is not None else "would-stop")
|
|
237
|
+
|
|
238
|
+
notifyd_pid = None
|
|
239
|
+
notifyd = "untouched"
|
|
240
|
+
if engine is None:
|
|
241
|
+
try:
|
|
242
|
+
notifyd_pid = _bound_notifyd_pid(store)
|
|
243
|
+
notifyd = "would-stop" if notifyd_pid is not None else "not-running"
|
|
244
|
+
except (CcxError, GenerationError, IdentityError, OSError) as error:
|
|
245
|
+
notifyd = "failed"
|
|
246
|
+
errors.append({"engine": None, "worker": "notifyd",
|
|
247
|
+
"short": "notifyd", "error": str(error)})
|
|
248
|
+
|
|
249
|
+
if dry_run:
|
|
250
|
+
for row in claude_rows + codex_rows:
|
|
251
|
+
row.setdefault("action", "would-kill")
|
|
252
|
+
return {"dry_run": True, "claude": claude_rows, "codex": codex_rows,
|
|
253
|
+
"codexd": codexd, "notifyd": notifyd,
|
|
254
|
+
"errors": errors, "ok": not errors}
|
|
255
|
+
|
|
256
|
+
from ccx.ops.terminate import confirmed_kill_uuid
|
|
257
|
+
|
|
258
|
+
for engine, rows in (("claude", claude_rows), ("codex", codex_rows)):
|
|
259
|
+
for row in rows:
|
|
260
|
+
if row.get("action") == "failed":
|
|
261
|
+
continue
|
|
262
|
+
try:
|
|
263
|
+
result = confirmed_kill_uuid(store, row["worker"], timeout=timeout)
|
|
264
|
+
except (CcxError, GenerationError, IdentityError, OSError) as error:
|
|
265
|
+
row["action"] = "failed"
|
|
266
|
+
errors.append({"engine": engine, "worker": row["worker"],
|
|
267
|
+
"short": row["short"], "error": str(error)})
|
|
268
|
+
continue
|
|
269
|
+
row["action"] = "killed" if result["changed"] else "already-down"
|
|
270
|
+
row["state"] = result["state"]
|
|
271
|
+
|
|
272
|
+
if codexd.startswith("would-stop"):
|
|
273
|
+
try:
|
|
274
|
+
deadline = None if timeout is None else time.monotonic() + max(0.0, timeout)
|
|
275
|
+
codexd = (_stop_codexd(deadline, older_generation=older_generation)
|
|
276
|
+
if older_generation is not None else _stop_codexd(deadline))
|
|
277
|
+
except CcxError as error:
|
|
278
|
+
codexd = "failed"
|
|
279
|
+
errors.append({"engine": "codex", "worker": "codexd",
|
|
280
|
+
"short": "codexd", "error": str(error)})
|
|
281
|
+
if notifyd == "would-stop":
|
|
282
|
+
try:
|
|
283
|
+
notifyd = _stop_notifyd(notifyd_pid)
|
|
284
|
+
except CcxError as error:
|
|
285
|
+
notifyd = "failed"
|
|
286
|
+
errors.append({"engine": None, "worker": "notifyd",
|
|
287
|
+
"short": "notifyd", "error": str(error)})
|
|
288
|
+
return {"dry_run": False, "claude": claude_rows, "codex": codex_rows,
|
|
289
|
+
"codexd": codexd, "notifyd": notifyd,
|
|
290
|
+
"errors": errors, "ok": not errors}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# --- untrust -----------------------------------------------------------------
|