cctally 1.88.2 → 1.89.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 +32 -0
- package/bin/_cctally_cache.py +827 -37
- package/bin/_cctally_config.py +125 -0
- package/bin/_cctally_core.py +86 -2
- package/bin/_cctally_dashboard_cache_report.py +31 -0
- package/bin/_cctally_dashboard_conversation.py +26 -7
- package/bin/_cctally_dashboard_sources.py +51 -0
- package/bin/_cctally_db.py +626 -0
- package/bin/_cctally_doctor.py +84 -1
- package/bin/_cctally_journal.py +2 -1
- package/bin/_cctally_parser.py +42 -0
- package/bin/_cctally_quota.py +1358 -112
- package/bin/_cctally_record.py +249 -9
- package/bin/_cctally_setup.py +14 -5
- package/bin/_cctally_store.py +16 -1
- package/bin/_cctally_tui.py +16 -2
- package/bin/_cctally_update.py +9 -2
- package/bin/_lib_background_mcp.py +168 -0
- package/bin/_lib_cache_report.py +19 -2
- package/bin/_lib_codex_conversation.py +8 -0
- package/bin/_lib_codex_conversation_query.py +8 -7
- package/bin/_lib_conversation.py +105 -5
- package/bin/_lib_conversation_dispatch.py +15 -4
- package/bin/_lib_conversation_query.py +294 -2
- package/bin/_lib_dashboard_sources.py +5 -1
- package/bin/_lib_doctor.py +202 -1
- package/bin/_lib_jsonl.py +12 -0
- package/bin/_lib_quota_alert_axes.py +188 -0
- package/bin/_lib_quota_ledger.py +274 -0
- package/bin/_lib_snapshot_cache.py +36 -0
- package/bin/cctally +6 -3
- package/dashboard/static/assets/index-BgoYXdus.js +92 -0
- package/dashboard/static/assets/index-Ub8vwz1M.css +1 -0
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +4 -1
- package/dashboard/static/assets/index-B0ZCsoxI.css +0 -1
- package/dashboard/static/assets/index-Bvp8mxtz.js +0 -92
package/bin/_cctally_doctor.py
CHANGED
|
@@ -414,6 +414,65 @@ def _codex_lifecycle_activity_24h(
|
|
|
414
414
|
return records
|
|
415
415
|
|
|
416
416
|
|
|
417
|
+
def _codex_quota_verify_activity_24h(*, now_utc: "dt.datetime") -> dict:
|
|
418
|
+
"""Aggregate the detached `_codex-quota-verify` worker's 24h outcomes.
|
|
419
|
+
|
|
420
|
+
The worker's three streams are `/dev/null` and its exit code is observed by
|
|
421
|
+
nobody, so `hook-tick.log` is the only place its outcome can land — which is
|
|
422
|
+
why it writes there. `_codex_lifecycle_activity_24h` above cannot supply
|
|
423
|
+
this: worker lines carry no `source_root_key`, so its root filter drops
|
|
424
|
+
every one of them.
|
|
425
|
+
|
|
426
|
+
Same bounded-read contract as its sibling — timestamped records, aggregate
|
|
427
|
+
counters only, never session/prompt/response content.
|
|
428
|
+
"""
|
|
429
|
+
cutoff = now_utc - dt.timedelta(hours=24)
|
|
430
|
+
counts: dict = {
|
|
431
|
+
"success_count_24h": 0,
|
|
432
|
+
"error_count_24h": 0,
|
|
433
|
+
"spawn_failure_count_24h": 0,
|
|
434
|
+
"last_success_at": None,
|
|
435
|
+
}
|
|
436
|
+
for path in (
|
|
437
|
+
_cctally_core.HOOK_TICK_LOG_ROTATED_PATH,
|
|
438
|
+
_cctally_core.HOOK_TICK_LOG_PATH,
|
|
439
|
+
):
|
|
440
|
+
try:
|
|
441
|
+
lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
|
|
442
|
+
except OSError:
|
|
443
|
+
continue
|
|
444
|
+
for line in lines:
|
|
445
|
+
tokens = line.split()
|
|
446
|
+
if not tokens:
|
|
447
|
+
continue
|
|
448
|
+
try:
|
|
449
|
+
captured_at = parse_iso_datetime(
|
|
450
|
+
tokens[0], "codex quota verify log timestamp")
|
|
451
|
+
captured_at = captured_at.astimezone(dt.timezone.utc)
|
|
452
|
+
except (IndexError, ValueError, TypeError):
|
|
453
|
+
continue
|
|
454
|
+
if captured_at > now_utc or captured_at < cutoff:
|
|
455
|
+
continue
|
|
456
|
+
fields = {
|
|
457
|
+
token.split("=", 1)[0]: token.split("=", 1)[1]
|
|
458
|
+
for token in tokens[1:] if "=" in token
|
|
459
|
+
}
|
|
460
|
+
if fields.get("provider") != "codex":
|
|
461
|
+
continue
|
|
462
|
+
op = fields.get("op")
|
|
463
|
+
outcome = fields.get("result")
|
|
464
|
+
if op == "quota-verify" and outcome == "success":
|
|
465
|
+
counts["success_count_24h"] += 1
|
|
466
|
+
prior = counts["last_success_at"]
|
|
467
|
+
if prior is None or captured_at > prior:
|
|
468
|
+
counts["last_success_at"] = captured_at
|
|
469
|
+
elif op == "quota-verify" and outcome == "error":
|
|
470
|
+
counts["error_count_24h"] += 1
|
|
471
|
+
elif op == "quota-verify-spawn" and outcome == "failed":
|
|
472
|
+
counts["spawn_failure_count_24h"] += 1
|
|
473
|
+
return counts
|
|
474
|
+
|
|
475
|
+
|
|
417
476
|
def _gather_accounts_state(now_utc: "dt.datetime") -> dict:
|
|
418
477
|
"""Best-effort account-attribution state for the doctor `accounts.*` legs
|
|
419
478
|
(#341). Never raises: identity + registry reads are read-only and each guard
|
|
@@ -1120,6 +1179,12 @@ def _doctor_gather_state_impl(
|
|
|
1120
1179
|
except Exception:
|
|
1121
1180
|
codex_lifecycle_activity_24h = {}
|
|
1122
1181
|
|
|
1182
|
+
try:
|
|
1183
|
+
codex_quota_verify_activity = _codex_quota_verify_activity_24h(
|
|
1184
|
+
now_utc=now_utc)
|
|
1185
|
+
except Exception:
|
|
1186
|
+
codex_quota_verify_activity = None
|
|
1187
|
+
|
|
1123
1188
|
# ── Parse health (#279 S2 F5a) ───────────────────────────────────
|
|
1124
1189
|
parse_health_claude = parse_health_codex = None
|
|
1125
1190
|
# #416 review B4: the durable record that a torn Codex `auth.json` halted
|
|
@@ -1131,19 +1196,30 @@ def _doctor_gather_state_impl(
|
|
|
1131
1196
|
# from the kernel constants, never inline literals.
|
|
1132
1197
|
codex_replay_pending = None
|
|
1133
1198
|
codex_replay_blocked = None
|
|
1199
|
+
# public #5: the budgeted-decline record. Same JSON-dict contract as the
|
|
1200
|
+
# blocked one, and the only signal a hook-only install produces when its
|
|
1201
|
+
# Codex ingest is frozen behind an un-runnable replay.
|
|
1202
|
+
codex_replay_deferred = None
|
|
1203
|
+
# public #5 spec §5: the hook's budgeted-ingest backlog record. Absent means
|
|
1204
|
+
# a zero backlog — a drained walk DELETES the row rather than zeroing it, so
|
|
1205
|
+
# None and "nothing owed" are the same state by construction.
|
|
1206
|
+
codex_ingest_backlog = None
|
|
1134
1207
|
try:
|
|
1135
1208
|
import _lib_codex_conversation as _codex_kern
|
|
1136
1209
|
_blocked_key = _codex_kern.CODEX_REPLAY_BLOCKED_KEY
|
|
1137
1210
|
_pending_key = _codex_kern.CODEX_REPLAY_FROM_ZERO_KEY
|
|
1211
|
+
_deferred_key = _codex_kern.CODEX_REPLAY_DEFERRED_KEY
|
|
1138
1212
|
except Exception:
|
|
1139
1213
|
_blocked_key = "codex_replay_from_zero_blocked"
|
|
1140
1214
|
_pending_key = "codex_replay_from_zero_pending"
|
|
1215
|
+
_deferred_key = "codex_replay_from_zero_deferred"
|
|
1141
1216
|
try:
|
|
1142
1217
|
if _cache_probe_allowed and _cctally_core.CACHE_DB_PATH.exists():
|
|
1143
1218
|
conn = sqlite3.connect(str(_cctally_core.CACHE_DB_PATH))
|
|
1144
1219
|
try:
|
|
1145
1220
|
for _key in ("parse_health_claude", "parse_health_codex",
|
|
1146
|
-
"codex_torn_auth_deferred", _blocked_key
|
|
1221
|
+
"codex_torn_auth_deferred", _blocked_key,
|
|
1222
|
+
_deferred_key, "codex_ingest_backlog"):
|
|
1147
1223
|
try:
|
|
1148
1224
|
row = conn.execute(
|
|
1149
1225
|
"SELECT value FROM cache_meta WHERE key = ?",
|
|
@@ -1158,6 +1234,10 @@ def _doctor_gather_state_impl(
|
|
|
1158
1234
|
parse_health_codex = _parsed
|
|
1159
1235
|
elif _key == _blocked_key:
|
|
1160
1236
|
codex_replay_blocked = _parsed
|
|
1237
|
+
elif _key == _deferred_key:
|
|
1238
|
+
codex_replay_deferred = _parsed
|
|
1239
|
+
elif _key == "codex_ingest_backlog":
|
|
1240
|
+
codex_ingest_backlog = _parsed
|
|
1161
1241
|
else:
|
|
1162
1242
|
codex_torn_deferred = _parsed
|
|
1163
1243
|
except (sqlite3.OperationalError, ValueError):
|
|
@@ -1744,8 +1824,10 @@ def _doctor_gather_state_impl(
|
|
|
1744
1824
|
parse_health_claude=parse_health_claude,
|
|
1745
1825
|
parse_health_codex=parse_health_codex,
|
|
1746
1826
|
codex_torn_deferred=codex_torn_deferred,
|
|
1827
|
+
codex_ingest_backlog=codex_ingest_backlog,
|
|
1747
1828
|
codex_replay_pending=codex_replay_pending,
|
|
1748
1829
|
codex_replay_blocked=codex_replay_blocked,
|
|
1830
|
+
codex_replay_deferred=codex_replay_deferred,
|
|
1749
1831
|
stats_db_quick_check=stats_db_quick_check,
|
|
1750
1832
|
cache_db_quick_check=cache_db_quick_check,
|
|
1751
1833
|
conversations_db_quick_check=conversations_db_quick_check,
|
|
@@ -1765,6 +1847,7 @@ def _doctor_gather_state_impl(
|
|
|
1765
1847
|
codex_quota_windows=codex_quota_windows,
|
|
1766
1848
|
codex_hook_roots=codex_hook_roots,
|
|
1767
1849
|
codex_lifecycle_activity_24h=codex_lifecycle_activity_24h,
|
|
1850
|
+
codex_quota_verify_activity=codex_quota_verify_activity,
|
|
1768
1851
|
# #311: precomputed statusLine.refreshInterval classification.
|
|
1769
1852
|
statusline_refresh_state=statusline_refresh_state,
|
|
1770
1853
|
statusline_pipeline=statusline_pipeline,
|
package/bin/_cctally_journal.py
CHANGED
|
@@ -4244,6 +4244,7 @@ _REBUILD_REQUIRED_TABLES = frozenset(
|
|
|
4244
4244
|
"projected_milestones",
|
|
4245
4245
|
"quota_alert_arming",
|
|
4246
4246
|
"quota_percent_milestones",
|
|
4247
|
+
"quota_projection_ledger_state",
|
|
4247
4248
|
"quota_projection_state",
|
|
4248
4249
|
"quota_threshold_events",
|
|
4249
4250
|
"quota_window_blocks",
|
|
@@ -4298,7 +4299,7 @@ _REBUILD_REQUIRED_INDEXES = frozenset(
|
|
|
4298
4299
|
# omitted column, constraint, partial predicate, or index definition. An epoch
|
|
4299
4300
|
# schema change must update this contract alongside STATS_INDEX_EPOCH.
|
|
4300
4301
|
_REBUILD_SCHEMA_FINGERPRINT = (
|
|
4301
|
-
"
|
|
4302
|
+
"1e0a8cc22b3dc754cb8a6074ff9d2ef28df77b656dfc65347e4afbcb6edfdfae"
|
|
4302
4303
|
)
|
|
4303
4304
|
|
|
4304
4305
|
|
package/bin/_cctally_parser.py
CHANGED
|
@@ -3337,6 +3337,46 @@ def _build_telemetry_beat_parser(subparsers, name, *, help_text, xref=None):
|
|
|
3337
3337
|
)
|
|
3338
3338
|
tb.set_defaults(func=c.cmd_telemetry_beat_internal)
|
|
3339
3339
|
|
|
3340
|
+
def _build_codex_quota_verify_parser(subparsers, name, *, help_text, xref=None):
|
|
3341
|
+
"""Build the `_codex-quota-verify` parser (public #5 spec §2)."""
|
|
3342
|
+
c = _cctally()
|
|
3343
|
+
qv = subparsers.add_parser(
|
|
3344
|
+
name,
|
|
3345
|
+
help=help_text,
|
|
3346
|
+
formatter_class=CLIHelpFormatter,
|
|
3347
|
+
description=textwrap.dedent(
|
|
3348
|
+
"""\
|
|
3349
|
+
Internal subcommand: detached worker that runs the Codex
|
|
3350
|
+
quota projection's periodic whole-history verification
|
|
3351
|
+
pass. Spawned by the Codex hook tick when the pass comes
|
|
3352
|
+
due, so the one unbounded operation in the incremental
|
|
3353
|
+
design never runs on the blocking hook path. Always returns
|
|
3354
|
+
0; the deadline is stamped only if the pass completes.
|
|
3355
|
+
"""
|
|
3356
|
+
),
|
|
3357
|
+
)
|
|
3358
|
+
qv.set_defaults(func=c.cmd_codex_quota_verify_internal)
|
|
3359
|
+
|
|
3360
|
+
def _build_codex_replay_drain_parser(subparsers, name, *, help_text, xref=None):
|
|
3361
|
+
"""Build the `_codex-replay-drain` parser (public #5 §4)."""
|
|
3362
|
+
c = _cctally()
|
|
3363
|
+
rd = subparsers.add_parser(
|
|
3364
|
+
name,
|
|
3365
|
+
help=help_text,
|
|
3366
|
+
formatter_class=CLIHelpFormatter,
|
|
3367
|
+
description=textwrap.dedent(
|
|
3368
|
+
"""\
|
|
3369
|
+
Internal subcommand: detached worker that performs the
|
|
3370
|
+
byte-zero Codex replay a budgeted hook tick declined. The
|
|
3371
|
+
replay is not sliceable, so the hook never attempts one and
|
|
3372
|
+
a hook-only install would otherwise freeze all Codex ingest
|
|
3373
|
+
permanently. Always returns 0; failures are written to
|
|
3374
|
+
hook-tick.log.
|
|
3375
|
+
"""
|
|
3376
|
+
),
|
|
3377
|
+
)
|
|
3378
|
+
rd.set_defaults(func=c.cmd_codex_replay_drain_internal)
|
|
3379
|
+
|
|
3340
3380
|
def _build_repair_symlinks_parser(subparsers, name, *, help_text, xref=None):
|
|
3341
3381
|
"""Build the `repair-symlinks` parser (registered via _REGISTRATION; #279 S6 W3).
|
|
3342
3382
|
|
|
@@ -3417,6 +3457,8 @@ _REGISTRATION = (
|
|
|
3417
3457
|
_Reg('update', _build_update_parser, "Update cctally to the latest version", None, None),
|
|
3418
3458
|
_Reg('_update-check', _build_update_check_parser, argparse.SUPPRESS, None, None),
|
|
3419
3459
|
_Reg('_telemetry-beat', _build_telemetry_beat_parser, argparse.SUPPRESS, None, None),
|
|
3460
|
+
_Reg('_codex-quota-verify', _build_codex_quota_verify_parser, argparse.SUPPRESS, None, None),
|
|
3461
|
+
_Reg('_codex-replay-drain', _build_codex_replay_drain_parser, argparse.SUPPRESS, None, None),
|
|
3420
3462
|
_Reg('repair-symlinks', _build_repair_symlinks_parser, argparse.SUPPRESS, None, None),
|
|
3421
3463
|
)
|
|
3422
3464
|
|