cctally 1.78.0 → 1.79.1
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/CHANGELOG.md +18 -0
- package/README.md +2 -0
- package/bin/_cctally_cache.py +140 -2
- package/bin/_cctally_config.py +50 -0
- package/bin/_cctally_dashboard.py +177 -20
- package/bin/_cctally_dashboard_conversation.py +1 -1
- package/bin/_cctally_dashboard_envelope.py +12 -0
- package/bin/_cctally_dashboard_sources.py +39 -11
- package/bin/_cctally_doctor.py +16 -1
- package/bin/_cctally_milestone_history.py +966 -0
- package/bin/_cctally_tui.py +23 -0
- package/bin/_cctally_update.py +338 -15
- package/bin/_cctally_weekrefs.py +6 -2
- package/bin/_lib_codex_conversation.py +1342 -18
- package/bin/_lib_codex_conversation_export.py +21 -1
- package/bin/_lib_codex_conversation_query.py +530 -67
- package/bin/_lib_conversation_dispatch.py +2 -2
- package/bin/_lib_doctor.py +38 -0
- package/bin/_lib_milestone_history.py +81 -0
- package/bin/cctally +31 -0
- package/dashboard/static/assets/index-CrIlpAlQ.css +1 -0
- package/dashboard/static/assets/index-Dc2u8c52.js +92 -0
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +3 -1
- package/dashboard/static/assets/index-CkTe6VJt.js +0 -87
- package/dashboard/static/assets/index-DsEXuMpq.css +0 -1
|
@@ -38,6 +38,7 @@ from _lib_dashboard_sources import (
|
|
|
38
38
|
dashboard_resource_key,
|
|
39
39
|
)
|
|
40
40
|
from _lib_quota import (
|
|
41
|
+
QuotaWindowIdentity,
|
|
41
42
|
build_blocks,
|
|
42
43
|
build_history,
|
|
43
44
|
forecast_quota,
|
|
@@ -94,6 +95,9 @@ class CodexCycleBoundary:
|
|
|
94
95
|
# Root provenance is server-only accounting input, never public wire data.
|
|
95
96
|
source_root_keys: tuple[str, ...]
|
|
96
97
|
used_percent: float | None = None
|
|
98
|
+
# Exact server-side quota identity selected for the hero. It is never
|
|
99
|
+
# serialized; milestone-history keys hash it opaquely.
|
|
100
|
+
quota_identity: QuotaWindowIdentity | None = None
|
|
97
101
|
|
|
98
102
|
|
|
99
103
|
@dataclass(frozen=True)
|
|
@@ -126,7 +130,7 @@ def _resolve_codex_weekly_cycle(
|
|
|
126
130
|
now_utc: dt.datetime,
|
|
127
131
|
) -> CodexCycleBoundary:
|
|
128
132
|
"""Select exactly one active account-level 10,080-minute native cycle."""
|
|
129
|
-
boundaries: dict[tuple[int, dt.datetime],
|
|
133
|
+
boundaries: dict[tuple[int, dt.datetime], list[tuple[object, object]]] = {}
|
|
130
134
|
stale_weekly_evidence = False
|
|
131
135
|
for history in build_history(tuple(observations)):
|
|
132
136
|
if history.identity.window_minutes != 10_080:
|
|
@@ -140,23 +144,33 @@ def _resolve_codex_weekly_cycle(
|
|
|
140
144
|
stale_weekly_evidence = True
|
|
141
145
|
continue
|
|
142
146
|
boundary = (history.identity.window_minutes, baseline.resets_at)
|
|
143
|
-
|
|
144
|
-
item["roots"].add(history.identity.source_root_key)
|
|
145
|
-
if baseline.used_percent is not None:
|
|
146
|
-
item["used"].append(float(baseline.used_percent))
|
|
147
|
+
boundaries.setdefault(boundary, []).append((history, baseline))
|
|
147
148
|
if len(boundaries) != 1:
|
|
148
149
|
if not boundaries:
|
|
149
150
|
raise CodexCycleUnavailable("stale" if stale_weekly_evidence else "missing")
|
|
150
151
|
raise CodexCycleUnavailable("conflicting")
|
|
151
|
-
(window_minutes, resets_at),
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
(window_minutes, resets_at), candidates = next(iter(boundaries.items()))
|
|
153
|
+
# Preserve the existing hero's max-used-percent choice, then pin every
|
|
154
|
+
# remaining tie deterministically. The selected full identity—not the
|
|
155
|
+
# union of sibling roots/slots/limits—owns the hero cycle.
|
|
156
|
+
history, baseline = max(
|
|
157
|
+
candidates,
|
|
158
|
+
key=lambda item: (
|
|
159
|
+
float(item[1].used_percent),
|
|
160
|
+
item[1].captured_at.astimezone(UTC),
|
|
161
|
+
item[0].identity.source_root_key,
|
|
162
|
+
item[0].identity.logical_limit_key,
|
|
163
|
+
item[0].identity.observed_slot,
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
selected_identity = history.identity
|
|
154
167
|
return CodexCycleBoundary(
|
|
155
168
|
window_minutes=window_minutes,
|
|
156
169
|
start_at=resets_at - dt.timedelta(minutes=window_minutes),
|
|
157
170
|
resets_at=resets_at,
|
|
158
|
-
source_root_keys=
|
|
159
|
-
used_percent=
|
|
171
|
+
source_root_keys=(selected_identity.source_root_key,),
|
|
172
|
+
used_percent=float(baseline.used_percent),
|
|
173
|
+
quota_identity=selected_identity,
|
|
160
174
|
)
|
|
161
175
|
|
|
162
176
|
|
|
@@ -2080,7 +2094,21 @@ def build_codex_source_state(
|
|
|
2080
2094
|
now_utc=context.now_utc,
|
|
2081
2095
|
display_tz_name=context.display_tz_name,
|
|
2082
2096
|
)
|
|
2083
|
-
|
|
2097
|
+
# Hero-modal historical-milestone navigation index (spec §1c, §3). Built
|
|
2098
|
+
# here on the non-idle codex source rebuild (idle ticks reuse the stored
|
|
2099
|
+
# bundle) over the durable projection — a pure serializer never touches it.
|
|
2100
|
+
# Guarded: an index failure must never fail the codex source build.
|
|
2101
|
+
cycle_index: tuple = ()
|
|
2102
|
+
if cycle is not None and not hero_failure:
|
|
2103
|
+
try:
|
|
2104
|
+
cycle_index = tuple(
|
|
2105
|
+
sys.modules["cctally"].build_codex_cycle_index(
|
|
2106
|
+
context.stats_conn, identity=cycle, now_utc=context.now_utc,
|
|
2107
|
+
)
|
|
2108
|
+
)
|
|
2109
|
+
except sqlite3.Error:
|
|
2110
|
+
cycle_index = ()
|
|
2111
|
+
quota = {**quota, "blocks": quota_blocks, "cycle_index": cycle_index}
|
|
2084
2112
|
budget_rows = _budget_wire(context.stats_conn)
|
|
2085
2113
|
projected_budget_rows = _projected_budget_wire(context.stats_conn)
|
|
2086
2114
|
budget_cost_events = _codex_budget_cost_events(context, budget_entries)
|
package/bin/_cctally_doctor.py
CHANGED
|
@@ -800,14 +800,28 @@ def doctor_gather_state(
|
|
|
800
800
|
# on corruption — both behaviors would hide diagnostic state
|
|
801
801
|
# (codex H1).
|
|
802
802
|
config_json_error = None
|
|
803
|
+
config_parsed: dict = {}
|
|
803
804
|
try:
|
|
804
805
|
if _cctally_core.CONFIG_PATH.exists():
|
|
805
|
-
json.loads(
|
|
806
|
+
config_parsed = json.loads(
|
|
807
|
+
_cctally_core.CONFIG_PATH.read_text(encoding="utf-8")
|
|
808
|
+
)
|
|
806
809
|
except json.JSONDecodeError as exc:
|
|
807
810
|
config_json_error = f"{type(exc).__name__}: {exc}"
|
|
808
811
|
except OSError as exc:
|
|
809
812
|
config_json_error = f"OSError: {exc}"
|
|
810
813
|
|
|
814
|
+
# Configured update (release) channel (beta-channel, spec 2026-07-21 §3):
|
|
815
|
+
# derived from the SAME raw read (never load_config, which auto-creates on
|
|
816
|
+
# first run). Fail-soft to "stable" — resolve_update_channel already
|
|
817
|
+
# tolerates a non-dict block / junk value.
|
|
818
|
+
try:
|
|
819
|
+
update_channel = c.resolve_update_channel(
|
|
820
|
+
config_parsed if isinstance(config_parsed, dict) else {}
|
|
821
|
+
)
|
|
822
|
+
except Exception:
|
|
823
|
+
update_channel = "stable"
|
|
824
|
+
|
|
811
825
|
update_state = None
|
|
812
826
|
update_state_error = None
|
|
813
827
|
try:
|
|
@@ -880,6 +894,7 @@ def doctor_gather_state(
|
|
|
880
894
|
codex_jsonl_present=codex_jsonl_present,
|
|
881
895
|
codex_project_metadata_health=codex_project_metadata_health,
|
|
882
896
|
codex_project_metadata_error=codex_project_metadata_error,
|
|
897
|
+
update_channel=update_channel,
|
|
883
898
|
dashboard_bind_stored=dashboard_bind_stored,
|
|
884
899
|
runtime_bind=runtime_bind,
|
|
885
900
|
# Conversation viewer (Plan 2, spec §5): only consulted on a LAN bind.
|