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,684 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Any
|
|
3
|
+
from ccx.errors import SessionUnavailable
|
|
4
|
+
from ccx.ops.turn_reports import codex_native_turn_evidence as _codex_native_turn_evidence
|
|
5
|
+
from ccx.notify.registry import NOTIFY_REPORT_MAX_CHARS, mutate_registration, now_ms, read_registration
|
|
6
|
+
from ccx.notify.select import _compact_effect_receipt, _future_events, _future_ref_before, _original_notification_registration
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _future_settlement(reg: dict[str, Any]) -> dict[str, Any] | None:
|
|
10
|
+
"""Select the first future native R once, then reuse exact receiptless proof."""
|
|
11
|
+
from ccx import constants
|
|
12
|
+
from ccx.claude.managed_turns import report_window_for_turn_ref, settlement_for_turn_ref
|
|
13
|
+
from ccx.ops.wait import wait_worker
|
|
14
|
+
from ccx.store.root import require_ccx_active
|
|
15
|
+
|
|
16
|
+
worker_id = reg["worker"]["workerId"]
|
|
17
|
+
child = f"claude:{worker_id}"
|
|
18
|
+
store = require_ccx_active(constants.CCX_ROOT)
|
|
19
|
+
op = reg["operation"]
|
|
20
|
+
cursor = op["armCursor"]
|
|
21
|
+
events = _future_events(worker_id, cursor)
|
|
22
|
+
ref = op["selectedTurnRef"]
|
|
23
|
+
if ref is None and events is not None:
|
|
24
|
+
seen: set[str] = set()
|
|
25
|
+
for row in events:
|
|
26
|
+
candidate = row.get("turn_ref")
|
|
27
|
+
if not isinstance(candidate, str) or not candidate.startswith("ccx-turn:claude:v1:"):
|
|
28
|
+
continue
|
|
29
|
+
if candidate not in seen:
|
|
30
|
+
seen.add(candidate)
|
|
31
|
+
if (row.get("type") in {"turn_start", "turn_identity_recovered"}
|
|
32
|
+
and not _future_ref_before(worker_id, cursor, candidate)):
|
|
33
|
+
def freeze(value: dict[str, Any]) -> None:
|
|
34
|
+
if (value["state"] == "armed"
|
|
35
|
+
and value["operation"]["selectedTurnRef"] is None):
|
|
36
|
+
value["operation"]["selectedTurnRef"] = candidate
|
|
37
|
+
updated = mutate_registration(reg["eventId"], freeze)
|
|
38
|
+
if updated is not None:
|
|
39
|
+
reg = updated
|
|
40
|
+
ref = reg["operation"]["selectedTurnRef"]
|
|
41
|
+
break
|
|
42
|
+
record = store.record(child)
|
|
43
|
+
state = (record.get("lifecycle") or {}).get("state")
|
|
44
|
+
base = {"workerState": state, "report": None, "error": None}
|
|
45
|
+
if ref is not None:
|
|
46
|
+
window, available = report_window_for_turn_ref(worker_id, ref)
|
|
47
|
+
offset = window.get("startOffset") if isinstance(window, dict) else None
|
|
48
|
+
if not available or type(offset) is not int or offset < 0:
|
|
49
|
+
# Late compact Stop and missed-hook turns are unknown, not
|
|
50
|
+
# a completed model turn or a permanently pending registration.
|
|
51
|
+
terminal, retained = settlement_for_turn_ref(worker_id, ref)
|
|
52
|
+
if retained and terminal and terminal.get("turnRef") == ref:
|
|
53
|
+
return {**base, "status": "unknown", "turnStatus": "unknown",
|
|
54
|
+
"turnRef": ref}
|
|
55
|
+
else:
|
|
56
|
+
# The selected turn is immutable across later turns and terminal retries.
|
|
57
|
+
result = _receiptless_settlement(reg)
|
|
58
|
+
if result is not None and result.get("status") != "waiting":
|
|
59
|
+
return result
|
|
60
|
+
# Gate is actionable only when an after-arm request and this same native R
|
|
61
|
+
# are both positively served; never adopt a pre-arm/foreign input gate.
|
|
62
|
+
if ref is not None and events is not None and any(
|
|
63
|
+
e.get("type") in {"permission_requested", "elicitation_requested"}
|
|
64
|
+
or (e.get("type") == "state" and e.get("to") == "waiting")
|
|
65
|
+
for e in events):
|
|
66
|
+
try:
|
|
67
|
+
status = wait_worker(child, timeout=.25,
|
|
68
|
+
expected_generation=store.generation)
|
|
69
|
+
except (SessionUnavailable, OSError):
|
|
70
|
+
status = {}
|
|
71
|
+
gate = status.get("waiting_for")
|
|
72
|
+
if (status.get("worker") == child and status.get("state") == "waiting"
|
|
73
|
+
and status.get("active_turn_ref") == ref
|
|
74
|
+
and isinstance(gate, dict) and gate.get("id")
|
|
75
|
+
and any((e.get("type") == "state" and e.get("to") == "waiting")
|
|
76
|
+
or (e.get("type") in {"permission_requested", "elicitation_requested"}
|
|
77
|
+
and str(e.get("request_id") or e.get("requestId")) == str(gate["id"]))
|
|
78
|
+
for e in events)):
|
|
79
|
+
return {**base, "status": "waiting", "turnStatus": "waiting",
|
|
80
|
+
"turnRef": ref, "workerState": "waiting", "waitingFor": gate}
|
|
81
|
+
if state in {"dead", "crashed", "idle-killed", "budget-stopped"}:
|
|
82
|
+
return {**base, "status": state, "turnStatus": None,
|
|
83
|
+
"error": (record.get("lifecycle") or {}).get("lastError")}
|
|
84
|
+
if state in {"idle", "busy", "waiting", "booting", "resuming"}:
|
|
85
|
+
from ccx.store.liveness import lifecycle_host_alive
|
|
86
|
+
from ccx.store.paths import sock_path
|
|
87
|
+
lifecycle = record.get("lifecycle") or {}
|
|
88
|
+
since = lifecycle.get("since")
|
|
89
|
+
pid = lifecycle.get("hostPid")
|
|
90
|
+
grace = 30_000 if state in {"booting", "resuming"} else 3_000
|
|
91
|
+
if (type(since) in {int, float} and now_ms() - since >= grace
|
|
92
|
+
and (not sock_path(worker_id).exists()
|
|
93
|
+
or (pid and not lifecycle_host_alive(lifecycle)))):
|
|
94
|
+
return {**base, "status": "host-dead", "turnStatus": None,
|
|
95
|
+
"error": "original Claude host is gone while future arm waits"}
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _receiptless_codex_report(store, child: str, worker_id: str, thread_id: str,
|
|
100
|
+
turn_id: str) -> str | None:
|
|
101
|
+
"""Only an already-serving original app-server may supply exact final text."""
|
|
102
|
+
from ccx.codex import runtime as codex_runtime
|
|
103
|
+
from ccx.ops.turn_reports import codex_source_report
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
reply = codex_runtime.daemon_call(
|
|
107
|
+
{"op": "thread-history", "workerId": worker_id,
|
|
108
|
+
"threadId": thread_id, "turnId": turn_id,
|
|
109
|
+
"observeSeconds": 2}, timeout=2,
|
|
110
|
+
short=child,
|
|
111
|
+
)
|
|
112
|
+
except (SessionUnavailable, OSError):
|
|
113
|
+
return None
|
|
114
|
+
if (reply.get("transport") != "served" or reply.get("ok") is not True
|
|
115
|
+
or reply.get("workerId") != worker_id or reply.get("threadId") != thread_id):
|
|
116
|
+
return None
|
|
117
|
+
try:
|
|
118
|
+
report = codex_source_report(
|
|
119
|
+
reply.get("thread"), worker_id, thread_id, turn_id,
|
|
120
|
+
expected_source=store.codex_source(child),
|
|
121
|
+
)
|
|
122
|
+
except SessionUnavailable:
|
|
123
|
+
return None
|
|
124
|
+
return report["text"] or None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _receiptless_settlement(reg: dict[str, Any]) -> dict[str, Any] | None:
|
|
128
|
+
"""Read the frozen native turn, never a guessed send receipt or newer turn."""
|
|
129
|
+
from ccx import constants
|
|
130
|
+
from ccx.ops.wait import wait_worker
|
|
131
|
+
from ccx.store.root import require_ccx_active
|
|
132
|
+
|
|
133
|
+
store = require_ccx_active(constants.CCX_ROOT)
|
|
134
|
+
worker = reg["worker"]
|
|
135
|
+
child = f"{worker['engine']}:{worker['workerId']}"
|
|
136
|
+
operation = reg["operation"]
|
|
137
|
+
ref = operation["selectedTurnRef"]
|
|
138
|
+
record = store.record(child)
|
|
139
|
+
base = {"workerState": (record.get("lifecycle") or {}).get("state"),
|
|
140
|
+
"report": None}
|
|
141
|
+
if worker["engine"] == "claude":
|
|
142
|
+
from ccx.claude.managed_turns import report_window_for_turn_ref, settlement_for_turn_ref
|
|
143
|
+
window, available = report_window_for_turn_ref(worker["workerId"], ref)
|
|
144
|
+
start = window.get("startOffset") if isinstance(window, dict) else None
|
|
145
|
+
if not available or type(start) is not int or start < 0:
|
|
146
|
+
return None # A fallback compact Stop is not a model completion.
|
|
147
|
+
base["turnRef"] = ref
|
|
148
|
+
terminal, available = settlement_for_turn_ref(worker["workerId"], ref)
|
|
149
|
+
if available and terminal is not None and terminal.get("turnRef") == ref:
|
|
150
|
+
if terminal.get("eventType") != "turn_complete":
|
|
151
|
+
status = "unknown" # turn_settled is liveness, never semantic success
|
|
152
|
+
else:
|
|
153
|
+
status = "interrupted" if terminal.get("interrupted") else "completed"
|
|
154
|
+
result = {**base, "status": status, "turnStatus": status,
|
|
155
|
+
"error": terminal.get("evidence") if status == "unknown" else None}
|
|
156
|
+
if status == "completed" and window.get("endOffsetKnown") is True:
|
|
157
|
+
result["report"], result["reportTruncated"] = _truncate_report(
|
|
158
|
+
_receiptless_claude_report(store, child, worker["workerId"], ref, window))
|
|
159
|
+
return result
|
|
160
|
+
else:
|
|
161
|
+
turn_id = operation["turnId"]
|
|
162
|
+
base["turnId"] = turn_id
|
|
163
|
+
proof = _codex_native_turn_evidence(child, record,
|
|
164
|
+
operation["nativeSourceId"], turn_id)
|
|
165
|
+
if proof is None:
|
|
166
|
+
return None
|
|
167
|
+
if proof["status"] is not None:
|
|
168
|
+
status = proof["status"]
|
|
169
|
+
report = (_receiptless_codex_report(store, child, worker["workerId"],
|
|
170
|
+
operation["nativeSourceId"], turn_id)
|
|
171
|
+
if status == "completed" else None)
|
|
172
|
+
report, truncated = _truncate_report(report)
|
|
173
|
+
return {**base, "status": status, "turnStatus": status,
|
|
174
|
+
"report": report, "reportTruncated": truncated, "error": proof["error"]}
|
|
175
|
+
# A gate is actionable only while the exact frozen native R remains active.
|
|
176
|
+
try:
|
|
177
|
+
snapshot = wait_worker(child, timeout=0, expected_generation=store.generation)
|
|
178
|
+
except (SessionUnavailable, OSError):
|
|
179
|
+
return None
|
|
180
|
+
store.revalidate()
|
|
181
|
+
if snapshot.get("worker") != child or snapshot.get("active_turn_ref") != ref:
|
|
182
|
+
return None
|
|
183
|
+
if snapshot.get("state") != "waiting":
|
|
184
|
+
return None
|
|
185
|
+
gate = (snapshot.get("waiting_for") if worker["engine"] == "claude" else
|
|
186
|
+
snapshot.get("pendingApprovals") or snapshot.get("pending_approvals"))
|
|
187
|
+
if worker["engine"] == "claude" and not (isinstance(gate, dict) and gate.get("id")):
|
|
188
|
+
return None
|
|
189
|
+
if worker["engine"] == "codex" and not gate:
|
|
190
|
+
return None
|
|
191
|
+
return {**base, "status": "waiting", "turnStatus": "waiting",
|
|
192
|
+
"workerState": "waiting", "waitingFor": gate}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _receiptless_claude_report(store, child: str, worker_id: str, ref: str,
|
|
196
|
+
window: dict[str, Any]) -> str | None:
|
|
197
|
+
from ccx.ops.turn_reports import claude_source_report
|
|
198
|
+
from ccx.store.root import StorageIntegrityError
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
result = claude_source_report(store, child, worker_id, ref, window)
|
|
202
|
+
return result["text"] or None
|
|
203
|
+
except (StorageIntegrityError, OSError, ValueError, SessionUnavailable):
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _truncate_report(text: str | None) -> tuple[str | None, bool]:
|
|
208
|
+
if text is None:
|
|
209
|
+
return None, False
|
|
210
|
+
if NOTIFY_REPORT_MAX_CHARS <= 0 or len(text) <= NOTIFY_REPORT_MAX_CHARS:
|
|
211
|
+
return text, False
|
|
212
|
+
return text[:NOTIFY_REPORT_MAX_CHARS], True
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _compact_observation(reg: dict[str, Any]) -> dict[str, Any]:
|
|
216
|
+
"""One read-only original-operation view; never start or publish a compact."""
|
|
217
|
+
from ccx.errors import CcxError
|
|
218
|
+
from ccx.store.root import GenerationError
|
|
219
|
+
store, _parent, child = _original_notification_registration(reg)
|
|
220
|
+
operation = reg["operation"]
|
|
221
|
+
engine = reg["worker"]["engine"]
|
|
222
|
+
operation_id = operation["operationId"]
|
|
223
|
+
receipt = _compact_effect_receipt(
|
|
224
|
+
store, child, engine, reg["worker"]["workerId"], operation)
|
|
225
|
+
state = receipt.get("state") if receipt else None
|
|
226
|
+
if state in (None, "prepared"):
|
|
227
|
+
return {"operationStatus": "not-started", "terminal": False}
|
|
228
|
+
if state == "rejected":
|
|
229
|
+
return {"operationStatus": "rejected", "terminal": True}
|
|
230
|
+
try:
|
|
231
|
+
if engine == "claude":
|
|
232
|
+
from ccx.ops.compact import compact_status
|
|
233
|
+
view = compact_status(child, operation_id=operation_id, publish=False)
|
|
234
|
+
status = view.get("operation_status")
|
|
235
|
+
terminal = status in {"succeeded", "noop", "failed"} or (
|
|
236
|
+
status == "unknown" and not str(view.get("reason") or "").startswith(
|
|
237
|
+
"original Claude compact source unavailable"))
|
|
238
|
+
return {**view, "operationStatus": status, "terminal": terminal}
|
|
239
|
+
from ccx.codex.ops import compact_observation
|
|
240
|
+
view = compact_observation(child, operation_id)
|
|
241
|
+
status = view.get("status")
|
|
242
|
+
record = store.record(child)
|
|
243
|
+
last = record.get("lastCompact")
|
|
244
|
+
last_terminal = (isinstance(last, dict)
|
|
245
|
+
and last.get("operationId") == operation_id
|
|
246
|
+
and (last.get("status") == "unknown"
|
|
247
|
+
or last.get("nativeTurnSettled") is True))
|
|
248
|
+
return {**view, "operationStatus": status,
|
|
249
|
+
"terminal": (status in {"succeeded", "failed"} or
|
|
250
|
+
(status == "unknown" and last_terminal))}
|
|
251
|
+
except (CcxError, GenerationError, OSError, ValueError):
|
|
252
|
+
# Lost original evidence is never a compact completion.
|
|
253
|
+
return {"operationStatus": "unknown", "terminal": False}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _compact_settlement(reg: dict[str, Any]) -> dict[str, Any] | None:
|
|
257
|
+
"""Freeze only the original native compact's proven verdict, never a model R."""
|
|
258
|
+
viewed = _compact_observation(reg)
|
|
259
|
+
if not viewed["terminal"]:
|
|
260
|
+
return None
|
|
261
|
+
status = viewed["operationStatus"]
|
|
262
|
+
result = {
|
|
263
|
+
"status": "completed" if status in {"succeeded", "noop"} else status,
|
|
264
|
+
"workerState": (_original_notification_registration(reg)[0].record(
|
|
265
|
+
f"{reg['worker']['engine']}:{reg['worker']['workerId']}"
|
|
266
|
+
).get("lifecycle") or {}).get("state"),
|
|
267
|
+
"operationType": "compact",
|
|
268
|
+
"operationId": reg["operation"]["operationId"],
|
|
269
|
+
"operationStatus": status,
|
|
270
|
+
"compacted": (status == "succeeded" if status in {"succeeded", "noop"}
|
|
271
|
+
else None),
|
|
272
|
+
"report": None,
|
|
273
|
+
"error": viewed.get("reason") if status in {"failed", "unknown"} else None,
|
|
274
|
+
}
|
|
275
|
+
if reg["worker"]["engine"] == "claude":
|
|
276
|
+
result.update(message=viewed.get("message"),
|
|
277
|
+
preTokens=viewed.get("pre_tokens"),
|
|
278
|
+
postTokens=viewed.get("post_tokens"),
|
|
279
|
+
durationMs=viewed.get("duration_ms"),
|
|
280
|
+
evidenceId=viewed.get("evidence_uuid"))
|
|
281
|
+
else:
|
|
282
|
+
result.update(compactTurnId=viewed.get("compactTurnId"),
|
|
283
|
+
contextItemId=viewed.get("contextItemId"),
|
|
284
|
+
nativeTurnStatus=viewed.get("nativeTurnStatus"),
|
|
285
|
+
contextBefore=viewed.get("contextBefore"),
|
|
286
|
+
contextAfter=viewed.get("contextAfter"))
|
|
287
|
+
return result
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _settlement_wake_marker(
|
|
291
|
+
reg: dict[str, Any], *, active: bool, pending_wake: bool,
|
|
292
|
+
last_turn_id: str | None
|
|
293
|
+
) -> int | None:
|
|
294
|
+
"""Persist/clear the durable wake-grace start for recursive settlement."""
|
|
295
|
+
event_id = reg.get("eventId")
|
|
296
|
+
if not isinstance(event_id, str) or not event_id:
|
|
297
|
+
return None
|
|
298
|
+
state = reg.get("settlementState")
|
|
299
|
+
state = state if isinstance(state, dict) else {}
|
|
300
|
+
current = state.get("pendingWakeSinceMs")
|
|
301
|
+
if active or not pending_wake:
|
|
302
|
+
if current is not None:
|
|
303
|
+
def clear(value: dict[str, Any]) -> None:
|
|
304
|
+
settlement_state = value.get("settlementState")
|
|
305
|
+
if isinstance(settlement_state, dict):
|
|
306
|
+
settlement_state.pop("pendingWakeSinceMs", None)
|
|
307
|
+
if not settlement_state:
|
|
308
|
+
value.pop("settlementState", None)
|
|
309
|
+
mutate_registration(event_id, clear)
|
|
310
|
+
return None
|
|
311
|
+
if (type(current) is int and
|
|
312
|
+
state.get("lastTurnId") == last_turn_id):
|
|
313
|
+
return current
|
|
314
|
+
|
|
315
|
+
started = now_ms()
|
|
316
|
+
def mark(value: dict[str, Any]) -> None:
|
|
317
|
+
settlement_state = value.setdefault("settlementState", {})
|
|
318
|
+
if settlement_state.get("lastTurnId") != last_turn_id:
|
|
319
|
+
settlement_state["pendingWakeSinceMs"] = started
|
|
320
|
+
else:
|
|
321
|
+
settlement_state.setdefault("pendingWakeSinceMs", started)
|
|
322
|
+
settlement_state["lastTurnId"] = last_turn_id
|
|
323
|
+
updated = mutate_registration(event_id, mark)
|
|
324
|
+
persisted = (
|
|
325
|
+
(updated.get("settlementState") or {}).get("pendingWakeSinceMs")
|
|
326
|
+
if isinstance(updated, dict)
|
|
327
|
+
else None
|
|
328
|
+
)
|
|
329
|
+
return persisted if isinstance(persisted, int) else started
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _recursive_codex_settlement(
|
|
333
|
+
reg: dict[str, Any],
|
|
334
|
+
base: dict[str, Any],
|
|
335
|
+
registration_snapshot: list[dict[str, Any]] | None = None,
|
|
336
|
+
) -> dict[str, Any] | None:
|
|
337
|
+
worker = reg.get("worker") if isinstance(reg.get("worker"), dict) else {}
|
|
338
|
+
worker_id = worker["workerId"]
|
|
339
|
+
from ccx.codex.settle import DEFAULT_WAKE_GRACE_S, settlement_progress
|
|
340
|
+
|
|
341
|
+
progress = settlement_progress(f"codex:{worker_id}",
|
|
342
|
+
registrations=registration_snapshot)
|
|
343
|
+
active = bool(progress.get("activeTurnId"))
|
|
344
|
+
pending_wake = bool(progress.get("pendingWakeCount")) and not bool(
|
|
345
|
+
progress.get("outstandingCount")
|
|
346
|
+
)
|
|
347
|
+
wake_since = _settlement_wake_marker(
|
|
348
|
+
reg, active=active, pending_wake=pending_wake,
|
|
349
|
+
last_turn_id=(progress.get("lastTurn") or {}).get("turnId"))
|
|
350
|
+
if (
|
|
351
|
+
pending_wake
|
|
352
|
+
and not active
|
|
353
|
+
and isinstance(wake_since, int)
|
|
354
|
+
and now_ms() - wake_since >= int(DEFAULT_WAKE_GRACE_S * 1000)
|
|
355
|
+
):
|
|
356
|
+
return {
|
|
357
|
+
**base,
|
|
358
|
+
"status": "delivery_gap",
|
|
359
|
+
"settlementVerdict": "delivery_gap",
|
|
360
|
+
"settlement": {
|
|
361
|
+
"verdict": "delivery_gap",
|
|
362
|
+
"outstandingCount": progress.get("outstandingCount"),
|
|
363
|
+
"outstanding": progress.get("outstanding"),
|
|
364
|
+
"pendingWakeCount": progress.get("pendingWakeCount"),
|
|
365
|
+
"pendingWake": progress.get("pendingWake"),
|
|
366
|
+
},
|
|
367
|
+
"error": (
|
|
368
|
+
"a descendant final notification was delivered after the "
|
|
369
|
+
"target's last completed turn, but no wake turn was observed "
|
|
370
|
+
"inside the recursive settlement wake grace"
|
|
371
|
+
),
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
final = (
|
|
375
|
+
None
|
|
376
|
+
if progress.get("settlementVerdict") == "pending"
|
|
377
|
+
else progress
|
|
378
|
+
)
|
|
379
|
+
if final is None:
|
|
380
|
+
return None
|
|
381
|
+
|
|
382
|
+
_settlement_wake_marker(reg, active=True, pending_wake=False,
|
|
383
|
+
last_turn_id=(progress.get("lastTurn") or {}).get("turnId"))
|
|
384
|
+
verdict = final.get("settlementVerdict")
|
|
385
|
+
summary = {
|
|
386
|
+
"verdict": verdict,
|
|
387
|
+
"outstandingCount": final.get("outstandingCount"),
|
|
388
|
+
"outstanding": final.get("outstanding"),
|
|
389
|
+
"pendingWakeCount": final.get("pendingWakeCount"),
|
|
390
|
+
"pendingWake": final.get("pendingWake"),
|
|
391
|
+
}
|
|
392
|
+
if verdict == "settled":
|
|
393
|
+
return {
|
|
394
|
+
**base,
|
|
395
|
+
"settlementVerdict": "settled",
|
|
396
|
+
"settlement": summary,
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
**base,
|
|
400
|
+
"status": verdict,
|
|
401
|
+
"workerState": final.get("state") or base.get("workerState"),
|
|
402
|
+
"settlementVerdict": verdict,
|
|
403
|
+
"settlement": summary,
|
|
404
|
+
"error": final.get("error") or base.get("error"),
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _original_codex_settlement(reg: dict[str, Any]) -> dict[str, Any] | None:
|
|
409
|
+
"""Freeze only the fenced original semantic Codex delivery's outcome.
|
|
410
|
+
|
|
411
|
+
This is a receipt consumer, not a second native turn observer. An optional
|
|
412
|
+
single read-only native reconciliation uses the already accepted core.
|
|
413
|
+
"""
|
|
414
|
+
from ccx.codex import delivery as codex_delivery
|
|
415
|
+
from ccx.ops.wait import reconcile_turn, wait_worker
|
|
416
|
+
from ccx.errors import SessionUnavailable
|
|
417
|
+
|
|
418
|
+
store, _parent, child = _original_notification_registration(reg)
|
|
419
|
+
operation = reg["operation"]
|
|
420
|
+
token = operation["deliveryId"]
|
|
421
|
+
receipt = codex_delivery.read_receipt(child, token)
|
|
422
|
+
if receipt is None:
|
|
423
|
+
# Publication requires this original prepared receipt. Its later loss
|
|
424
|
+
# never turns an old cached report into a completed child result.
|
|
425
|
+
return None
|
|
426
|
+
if (receipt.get("promptSha256") != operation.get("promptSha256")
|
|
427
|
+
or receipt.get("requestedMode") != operation.get("requestedMode")
|
|
428
|
+
or receipt.get("callerMetadata") != operation.get("callerMetadata")
|
|
429
|
+
or receipt.get("clientUserMessageId") != operation.get("clientUserMessageId")):
|
|
430
|
+
raise SessionUnavailable("Notification does not match its delivery; inspect the worker")
|
|
431
|
+
base = {"workerState": (store.record(child).get("lifecycle") or {}).get("state"),
|
|
432
|
+
"turnId": receipt.get("turnId"), "report": None}
|
|
433
|
+
if receipt["state"] == "rejected":
|
|
434
|
+
return {**base, "status": "rejected", "turnStatus": "rejected",
|
|
435
|
+
"error": receipt.get("rejectionMessage") or receipt.get("rejectionCode")}
|
|
436
|
+
if receipt["state"] == "prepared":
|
|
437
|
+
# Registration follows prepare; this send has never reached a
|
|
438
|
+
# native call in this state. A retryable refusal is not a settlement.
|
|
439
|
+
return None
|
|
440
|
+
if receipt["state"] != "accepted":
|
|
441
|
+
# owned/submitted and lost-ACK remain unresolved, never completed.
|
|
442
|
+
return None
|
|
443
|
+
turn_id = receipt.get("turnId")
|
|
444
|
+
if (not isinstance(turn_id, str) or not turn_id
|
|
445
|
+
or operation.get("turnId") not in (None, turn_id)):
|
|
446
|
+
raise SessionUnavailable("Notification turn does not match its delivery; inspect the worker")
|
|
447
|
+
|
|
448
|
+
def completed(value: dict[str, Any]) -> dict[str, Any] | None:
|
|
449
|
+
proof = value.get("settlement")
|
|
450
|
+
native = store.record(child).get("native") or {}
|
|
451
|
+
if not isinstance(proof, dict):
|
|
452
|
+
return None
|
|
453
|
+
if (proof.get("workerId") != reg["worker"]["workerId"]
|
|
454
|
+
or proof.get("deliveryId") != token
|
|
455
|
+
or proof.get("turnId") != turn_id
|
|
456
|
+
or proof.get("threadId") != native.get("threadId")
|
|
457
|
+
or proof.get("nativeSource") != store.codex_source(child)
|
|
458
|
+
or proof.get("status") != "completed"
|
|
459
|
+
or not isinstance(proof.get("text"), str)
|
|
460
|
+
or not isinstance(proof.get("nativeEvidenceSha256"), str)
|
|
461
|
+
or len(proof["nativeEvidenceSha256"]) != 64):
|
|
462
|
+
raise SessionUnavailable("original Codex completed receipt lacks turn/source proof")
|
|
463
|
+
return {**base, "status": "completed", "turnStatus": "completed",
|
|
464
|
+
"turnId": turn_id, "report": proof["text"], "error": None}
|
|
465
|
+
|
|
466
|
+
existing = completed(receipt)
|
|
467
|
+
if existing is not None:
|
|
468
|
+
return existing
|
|
469
|
+
try:
|
|
470
|
+
observed = reconcile_turn(child, delivery_id=token,
|
|
471
|
+
expected_generation=store.generation)
|
|
472
|
+
except (SessionUnavailable, OSError):
|
|
473
|
+
observed = None
|
|
474
|
+
store.revalidate()
|
|
475
|
+
if isinstance(observed, dict):
|
|
476
|
+
row = observed.get("delivery") or {}
|
|
477
|
+
parent = observed.get("worker") or {}
|
|
478
|
+
if (row.get("delivery_id") == token and row.get("turn_id") == turn_id
|
|
479
|
+
and parent.get("worker_id") == reg["worker"]["workerId"]):
|
|
480
|
+
receipt = codex_delivery.read_receipt(child, token) or receipt
|
|
481
|
+
existing = completed(receipt)
|
|
482
|
+
if existing is not None:
|
|
483
|
+
return existing
|
|
484
|
+
native_outcome = observed.get("native_status")
|
|
485
|
+
if native_outcome in {"failed", "interrupted", "cancelled"}:
|
|
486
|
+
return {**base, "status": native_outcome,
|
|
487
|
+
"turnStatus": native_outcome, "turnId": turn_id,
|
|
488
|
+
"error": observed.get("native_error"), "report": None}
|
|
489
|
+
# Current gate/terminal is advisory; it must belong to the same accepted
|
|
490
|
+
# turn, and must never turn a submitted-unknown/old gate into completion.
|
|
491
|
+
try:
|
|
492
|
+
snapshot = wait_worker(child, timeout=0,
|
|
493
|
+
expected_generation=store.generation)
|
|
494
|
+
except (SessionUnavailable, OSError):
|
|
495
|
+
return None
|
|
496
|
+
store.revalidate()
|
|
497
|
+
if (snapshot.get("worker") != child
|
|
498
|
+
or snapshot.get("active_turn_ref") != codex_delivery.turn_ref_for(turn_id)):
|
|
499
|
+
return None
|
|
500
|
+
if snapshot.get("state") == "waiting":
|
|
501
|
+
pending = snapshot.get("pending_approvals") or snapshot.get("pendingApprovals") or []
|
|
502
|
+
return {**base, "status": "waiting", "turnStatus": "waiting",
|
|
503
|
+
"turnId": turn_id, "waitingFor": pending,
|
|
504
|
+
"workerState": "waiting", "report": None}
|
|
505
|
+
return None
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def _original_claude_settlement(reg: dict[str, Any]) -> dict[str, Any] | None:
|
|
509
|
+
"""Only original Claude receipt/Stop or its exact start-unique turn gate."""
|
|
510
|
+
from ccx.claude import delivery as claude_delivery
|
|
511
|
+
from ccx.errors import SessionUnavailable
|
|
512
|
+
from ccx.claude.managed_turns import settlement_for_turn_ref
|
|
513
|
+
from ccx.ops.wait import reconcile_turn, wait_worker
|
|
514
|
+
from ccx.store.root import NativeEffectIndeterminate
|
|
515
|
+
|
|
516
|
+
store, _parent, child = _original_notification_registration(reg)
|
|
517
|
+
operation = reg["operation"]
|
|
518
|
+
token = operation["deliveryId"]
|
|
519
|
+
try:
|
|
520
|
+
receipt = claude_delivery.read_receipt(child, token)
|
|
521
|
+
except claude_delivery.ClaudeDeliveryError as exc:
|
|
522
|
+
raise SessionUnavailable("original Claude receipt unreadable") from exc
|
|
523
|
+
if receipt is None:
|
|
524
|
+
return None
|
|
525
|
+
if (receipt.get("workerId") != reg["worker"]["workerId"]
|
|
526
|
+
or receipt.get("deliveryId") != token
|
|
527
|
+
or receipt.get("requestedMode") != operation["requestedMode"]
|
|
528
|
+
or receipt.get("promptSha256") != operation["promptSha256"]
|
|
529
|
+
or receipt.get("promptByteCount") != operation["promptByteCount"]
|
|
530
|
+
or receipt.get("cap") != operation["cap"]
|
|
531
|
+
or receipt.get("callerMetadata") != operation["callerMetadata"]):
|
|
532
|
+
raise SessionUnavailable("Claude notification belongs to another worker; check its ID")
|
|
533
|
+
native = store.record(child).get("native") or {}
|
|
534
|
+
sid = native.get("sessionId")
|
|
535
|
+
if not isinstance(sid, str) or not sid:
|
|
536
|
+
raise SessionUnavailable("original Claude notification native session unbound")
|
|
537
|
+
base = {"workerState": (store.record(child).get("lifecycle") or {}).get("state"),
|
|
538
|
+
"turnRef": receipt.get("turnRef"), "report": None}
|
|
539
|
+
if receipt["state"] == "rejected":
|
|
540
|
+
return {**base, "status": "rejected", "turnStatus": "rejected",
|
|
541
|
+
"error": receipt.get("rejectionMessage") or receipt.get("rejectionCode")}
|
|
542
|
+
# Prepared (including submitted-unknown host refusal) and host-owned
|
|
543
|
+
# before original placement have no proven native turn and never complete.
|
|
544
|
+
if receipt["state"] != "accepted":
|
|
545
|
+
return None
|
|
546
|
+
turn_ref = receipt.get("turnRef")
|
|
547
|
+
if (receipt.get("route") != "new_turn" or receipt.get("placement") != "new_turn"
|
|
548
|
+
or not isinstance(turn_ref, str)
|
|
549
|
+
or not turn_ref.startswith("ccx-turn:claude:v1:")
|
|
550
|
+
or len(turn_ref) <= len("ccx-turn:claude:v1:")):
|
|
551
|
+
return None
|
|
552
|
+
|
|
553
|
+
# Existing original reconciler already validates global intent, native
|
|
554
|
+
# SID/source/spec/turnRef and forward-repairs the two Stop/effect writes.
|
|
555
|
+
try:
|
|
556
|
+
observed = reconcile_turn(child, delivery_id=token,
|
|
557
|
+
expected_generation=store.generation)
|
|
558
|
+
except (SessionUnavailable, NativeEffectIndeterminate,
|
|
559
|
+
claude_delivery.ClaudeDeliveryError, OSError):
|
|
560
|
+
observed = None
|
|
561
|
+
store.revalidate()
|
|
562
|
+
if isinstance(observed, dict) and observed.get("status") == "settled":
|
|
563
|
+
row = observed.get("delivery") or {}
|
|
564
|
+
try:
|
|
565
|
+
receipt = claude_delivery.read_receipt(child, token) or receipt
|
|
566
|
+
except claude_delivery.ClaudeDeliveryError as exc:
|
|
567
|
+
raise SessionUnavailable("original Claude settlement unreadable") from exc
|
|
568
|
+
proof = receipt.get("settlement")
|
|
569
|
+
if (row.get("delivery_id") == token and row.get("turn_ref") == turn_ref
|
|
570
|
+
and isinstance(proof, dict) and proof.get("workerId") == reg["worker"]["workerId"]
|
|
571
|
+
and proof.get("deliveryId") == token and proof.get("turnRef") == turn_ref
|
|
572
|
+
and proof.get("nativeSource") == f"claude-session:{sid}"
|
|
573
|
+
and proof.get("status") == "completed"
|
|
574
|
+
and isinstance(proof.get("text"), str)):
|
|
575
|
+
return {**base, "status": "completed", "turnStatus": "completed",
|
|
576
|
+
"turnRef": turn_ref, "report": proof["text"], "error": None}
|
|
577
|
+
|
|
578
|
+
# A positive Stop receipt whose companion effect is still inconclusive
|
|
579
|
+
# cannot be replaced by a contradictory negative sidecar observation.
|
|
580
|
+
if isinstance(receipt.get("settlement"), dict):
|
|
581
|
+
return None
|
|
582
|
+
# Retained sidecar is exact by turnRef, and can close interrupted/unknown
|
|
583
|
+
# without inventing a final assistant message, failed or cancelled result.
|
|
584
|
+
terminal, available = settlement_for_turn_ref(reg["worker"]["workerId"], turn_ref)
|
|
585
|
+
if available and isinstance(terminal, dict) and terminal.get("turnRef") == turn_ref:
|
|
586
|
+
if terminal.get("outcome") in {"interrupted", "unknown"}:
|
|
587
|
+
status = terminal["outcome"]
|
|
588
|
+
return {**base, "status": status, "turnStatus": status,
|
|
589
|
+
"turnRef": turn_ref, "report": None,
|
|
590
|
+
"error": terminal.get("evidence")}
|
|
591
|
+
|
|
592
|
+
# A currently blocked gate is actionable only on this accepted native
|
|
593
|
+
# turn. An older/successor gate or host-owned pre-start slot cannot wake it.
|
|
594
|
+
try:
|
|
595
|
+
snapshot = wait_worker(child, timeout=0,
|
|
596
|
+
expected_generation=store.generation)
|
|
597
|
+
except (SessionUnavailable, OSError):
|
|
598
|
+
return None
|
|
599
|
+
store.revalidate()
|
|
600
|
+
waiting = snapshot.get("waiting_for")
|
|
601
|
+
if (snapshot.get("worker") == child and snapshot.get("state") == "waiting"
|
|
602
|
+
and snapshot.get("active_turn_ref") == turn_ref
|
|
603
|
+
and isinstance(waiting, dict) and waiting.get("id")):
|
|
604
|
+
return {**base, "status": "waiting", "turnStatus": "waiting",
|
|
605
|
+
"turnRef": turn_ref, "workerState": "waiting",
|
|
606
|
+
"waitingFor": waiting, "report": None}
|
|
607
|
+
return None
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def settlement_for_registration(
|
|
611
|
+
reg: dict[str, Any],
|
|
612
|
+
registration_snapshot: list[dict[str, Any]] | None = None,
|
|
613
|
+
) -> dict[str, Any] | None:
|
|
614
|
+
_original_notification_registration(reg)
|
|
615
|
+
if reg.get("settlementMode") == "orchestration":
|
|
616
|
+
# A notifier/client may hold an older snapshot. Read the persisted
|
|
617
|
+
# original freeze and wake clock before any new turn/report observation.
|
|
618
|
+
current = read_registration(reg["eventId"])
|
|
619
|
+
if current is None:
|
|
620
|
+
return None
|
|
621
|
+
_original_notification_registration(current)
|
|
622
|
+
reg = current
|
|
623
|
+
frozen = reg.get("operationSettlement")
|
|
624
|
+
if not isinstance(frozen, dict):
|
|
625
|
+
base = _original_notification_settlement(reg)
|
|
626
|
+
if base is None:
|
|
627
|
+
return None
|
|
628
|
+
def freeze(row):
|
|
629
|
+
if row.get("state") == "armed":
|
|
630
|
+
row.setdefault("operationSettlement", dict(base))
|
|
631
|
+
updated = mutate_registration(reg["eventId"], freeze)
|
|
632
|
+
if updated is None or not isinstance(updated.get("operationSettlement"), dict):
|
|
633
|
+
return None
|
|
634
|
+
reg = updated
|
|
635
|
+
frozen = reg["operationSettlement"]
|
|
636
|
+
return _recursive_codex_settlement(reg, frozen, registration_snapshot)
|
|
637
|
+
if reg["worker"]["engine"] == "controller":
|
|
638
|
+
from ccx.controllers.notify import settlement
|
|
639
|
+
return settlement(reg)
|
|
640
|
+
return _original_notification_settlement(reg)
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def _original_notification_settlement(reg: dict[str, Any]) -> dict[str, Any] | None:
|
|
644
|
+
if reg["source"] == "compact" or (
|
|
645
|
+
reg["source"] == "notify" and reg["operation"].get("kind") in {
|
|
646
|
+
"claude-compact", "codex-compact"}):
|
|
647
|
+
return _compact_settlement(reg)
|
|
648
|
+
if reg["source"] == "notify" and reg["operation"]["kind"] == "claude-next-turn":
|
|
649
|
+
return _future_settlement(reg)
|
|
650
|
+
if (reg["source"] in {"notify", "new"}
|
|
651
|
+
and reg["operation"].get("selectionMode") == "status-only"):
|
|
652
|
+
state = reg["operation"]["stableState"]
|
|
653
|
+
return {"status": state, "workerState": state,
|
|
654
|
+
"turnStatus": None, "report": None, "error": None}
|
|
655
|
+
if reg["source"] == "input":
|
|
656
|
+
_store, _parent, child = _original_notification_registration(reg)
|
|
657
|
+
operation = reg["operation"]
|
|
658
|
+
intent = _store.read_receipt(child, "answer", operation["responseId"])
|
|
659
|
+
if intent is None or intent["state"] != "accepted":
|
|
660
|
+
return None # A submitted-unknown / rejected gate is never completed.
|
|
661
|
+
if reg["worker"]["engine"] == "claude":
|
|
662
|
+
original = _store.record(child).get("waitingFor")
|
|
663
|
+
if isinstance(original, dict) and original.get("id") == operation["gateId"]:
|
|
664
|
+
return None # Original permission remains unresolved, not a new gate.
|
|
665
|
+
else:
|
|
666
|
+
current = _store.record(child).get("pendingApprovals") or []
|
|
667
|
+
if any(isinstance(gate, dict) and gate.get("gateId") == operation["gateId"]
|
|
668
|
+
and gate.get("turnId") == operation["turnId"] for gate in current):
|
|
669
|
+
return None
|
|
670
|
+
observed = _receiptless_settlement(reg)
|
|
671
|
+
if observed is not None and observed.get("status") == "waiting":
|
|
672
|
+
gates = observed.get("waitingFor") or []
|
|
673
|
+
if reg["worker"]["engine"] == "claude":
|
|
674
|
+
if not isinstance(gates, dict) or gates.get("id") in {None, operation["gateId"]}:
|
|
675
|
+
return None # A new actionable gate, on the same frozen R.
|
|
676
|
+
elif not any(isinstance(gate, dict) and gate.get("gateId") != operation["gateId"]
|
|
677
|
+
and gate.get("turnId") == operation["turnId"] for gate in gates):
|
|
678
|
+
return None
|
|
679
|
+
return observed
|
|
680
|
+
if reg["source"] == "notify" and reg["operation"].get("nativeProof") == "original-native-turn":
|
|
681
|
+
return _receiptless_settlement(reg)
|
|
682
|
+
if reg["worker"]["engine"] == "claude":
|
|
683
|
+
return _original_claude_settlement(reg)
|
|
684
|
+
return _original_codex_settlement(reg)
|