cctally 1.109.0 → 1.110.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/CHANGELOG.md +46 -0
- package/README.md +6 -6
- package/bin/_cctally_cache.py +62 -13
- package/bin/_cctally_core.py +5 -1
- package/bin/_cctally_dashboard.py +362 -41
- package/bin/_cctally_dashboard_conversation.py +231 -40
- package/bin/_cctally_dashboard_envelope.py +48 -32
- package/bin/_cctally_dashboard_sources.py +503 -109
- package/bin/_cctally_db.py +44 -0
- package/bin/_cctally_diagnosis_sources.py +181 -15
- package/bin/_cctally_doctor.py +15 -3
- package/bin/_cctally_forecast.py +25 -12
- package/bin/_cctally_journal.py +59 -32
- package/bin/_cctally_milestone_history.py +65 -9
- package/bin/_cctally_parser.py +5 -0
- package/bin/_cctally_record.py +50 -12
- package/bin/_cctally_rederive.py +899 -30
- package/bin/_cctally_source_analytics.py +567 -133
- package/bin/_cctally_transcript.py +27 -2
- package/bin/_cctally_tui.py +39 -6
- package/bin/_lib_codex_conversation_query.py +286 -63
- package/bin/_lib_codex_metadata.py +207 -0
- package/bin/_lib_conversation_query.py +254 -37
- package/bin/_lib_doctor.py +16 -2
- package/bin/_lib_pricing.py +69 -14
- package/bin/_lib_rederive.py +903 -2
- package/bin/_lib_view_models.py +5 -0
- package/dashboard/static/assets/ConversationsView-BB9vUiL6.js +72 -0
- package/dashboard/static/assets/{DoctorModal-BZGXOlc2.js → DoctorModal-DFY2iPDf.js} +1 -1
- package/dashboard/static/assets/ModalRoot-DvRKHbQE.js +1 -0
- package/dashboard/static/assets/{ProjectsDrillPanel-KbO_KWa7.js → ProjectsDrillPanel-DAv39uSo.js} +1 -1
- package/dashboard/static/assets/{SourceDetailModal-BrOPLVRL.js → SourceDetailModal-DSAWo2Fp.js} +1 -1
- package/dashboard/static/assets/{UpdateModal-CYgEFAi4.js → UpdateModal-R_9dOVnh.js} +3 -3
- package/dashboard/static/assets/index-COhxe3vU.css +1 -0
- package/dashboard/static/assets/index-DdU62sov.js +13 -0
- package/dashboard/static/assets/outlineNavigation-BOCLcRa6.js +9 -0
- package/dashboard/static/assets/{useKeymap-DQx_A1MB.js → useKeymap-CW7Up0Ay.js} +1 -1
- package/dashboard/static/dashboard.html +3 -3
- package/package.json +2 -1
- package/dashboard/static/assets/ConversationsView-TO5bpIyc.js +0 -72
- package/dashboard/static/assets/ModalRoot-C_fvugTl.js +0 -1
- package/dashboard/static/assets/index-Cj_l7T7u.css +0 -1
- package/dashboard/static/assets/index-DLVwACUM.js +0 -13
- package/dashboard/static/assets/outlineNavigation-DXX-RjKl.js +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,52 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.110.0] - 2026-09-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Claude cost reports price `claude-opus-5-5` at its published input, output, cache and Fast-mode rates instead of showing $0 for new usage.
|
|
12
|
+
- Codex cost reports price `gpt-6-luna` at its published Standard, long-context and Fast-mode rates, and resolve `gpt-daybreak-red-latest` to the current Cyber card.
|
|
13
|
+
- Historical `gpt-5.5-cyber` Codex turns use their retained LiteLLM OpenAI-provider rate card instead of the legacy `gpt-5` fallback.
|
|
14
|
+
- Codex reporting prices `gpt-6-sol` from OpenAI's published Standard, cached-input, long-context and Fast-mode rates instead of the legacy `gpt-5` fallback.
|
|
15
|
+
- The dashboard's `Blocks` panel marks Claude rows whose cost and model figures come from the facts retained when the block closed, without recalculating those figures from the current cache.
|
|
16
|
+
- Maintainers can turn latest full-backlog triage into a dependency-safe Codex execution plan and run automatically in two Sol/High worktree slots, with refresh-safe splitting, one resumable monitor, and no worker-created orchestration chain.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- Codex long-context requests above 272K input tokens now use OpenAI's higher input, cached-input and output rates for the entire request, including Fast-mode costs and dashboard cache savings.
|
|
20
|
+
- The dashboard promptly releases a Conversation Viewer live-tail thread when its reader closes, instead of retaining each one until the next 15-second keep-alive.
|
|
21
|
+
- The dashboard's Current Week modal reliably returns keyboard focus to the hero after Escape, even when a source-selector click is still pending under load.
|
|
22
|
+
- The `Blocks` detail modal connects its retained-facts explanation to the cost and token figures for screen readers.
|
|
23
|
+
- The `dashboard` fully hides its onboarding hint while an alert toast is active on a phone, so the alert action stays visible while the original onboarding timer continues.
|
|
24
|
+
- `cctally db rederive` retains a weekly cost snapshot when replay names the same range instants with different UTC-offset spelling, so reviewed corrections do not rewrite unchanged cost facts.
|
|
25
|
+
- `cctally db rederive` can apply an operator-reviewed weekly correction without carrying unrelated baseline drift. Version 2 manifests can preserve or release an accepted snapshot identity and pin both plan hashes before append.
|
|
26
|
+
- `cctally db rederive` preserves closed five-hour costs and accepted snapshot references when current replay rules revisit the same retained evidence.
|
|
27
|
+
- A Conversation Viewer link that waits for its outline now limits a failed page load to three requests, then shows the connection error with a Retry button for the linked message.
|
|
28
|
+
- `cctally db rederive` now refuses a correction that would add a burst of matching weekly reset events, and its JSON preview breaks action counts out by event kind for inspection.
|
|
29
|
+
- Dashboard diagnosis and conversation outlines now bound request surges and stalled reads. A timed-out diagnosis process is retired so later requests recover, and unchanged dashboard files revalidate without rereading or recompression.
|
|
30
|
+
- The dashboard's `Current Week` five-hour block now follows the account that supplied the displayed weekly percentage. Its credits and crossings stay with that account when two accounts share a window, including while browsing older blocks.
|
|
31
|
+
- The dashboard's Claude Session detail now shows cache rebuilds and cache savings over plain HTTP LAN access, including the healthy zero-rebuild state. Opening the same session over loopback and LAN shows the same verified outline.
|
|
32
|
+
- Dashboard panel headings now shorten before their action buttons escape narrow cards, including when Projects data is unavailable.
|
|
33
|
+
- The dashboard calls Claude's credited window a cycle in the hero and milestone modal. Its cost comparison names a comparable cycle, multi-account spend no longer implies one common cycle, and week-scoped panels keep their week labels.
|
|
34
|
+
- The dashboard's phone-width account cards now show a swipe cue while more cards remain, and long snapshot ages stay readable inside the hero.
|
|
35
|
+
- At phone widths where the dashboard source-status chip is shown, warning labels now name both the affected area and its state, such as `Accounts unavailable` or `Projects partial`.
|
|
36
|
+
- The dashboard's `Send test alert` preview now shows the same account chip and dismiss hint as a real alert on single-account and multi-account installs.
|
|
37
|
+
- Closing a dashboard modal now returns keyboard focus to its opener, and explanatory card text no longer opens a modal when clicked.
|
|
38
|
+
- The dashboard's All-source Daily detail shows a single-provider day's model costs once, without an empty second provider row.
|
|
39
|
+
- The dashboard's Projects modal presents its cycle window and chart scale as separate radio groups for screen readers.
|
|
40
|
+
- Corrupt Codex token or timestamp values no longer empty the provider. Valid costs and projects remain visible while affected rows are skipped and counted, including rows in a budget period older than the detail view.
|
|
41
|
+
- Long identifiers in Doctor remediation text now wrap inside the modal at narrow screen widths instead of being clipped.
|
|
42
|
+
- One Codex conversation whose stored project path cannot be read no longer empties the whole Codex provider. Every other project is published from current data, and the affected rows are reported as withheld.
|
|
43
|
+
- A Codex project path the store cannot read is counted and named in `cctally doctor`, under a third reason beside the missing key and missing thread join it already reports.
|
|
44
|
+
- A refresh that could not read project metadata is replaced on the next refresh instead of being kept for up to two minutes.
|
|
45
|
+
- A refresh whose project metadata could not be read states so in the Projects panel body, so an empty panel is no longer silent about why.
|
|
46
|
+
- The Conversation Viewer opens a conversation whose stored project path cannot be read, showing no project instead of failing, and its spawned-agent links, raw export and live tail work again.
|
|
47
|
+
- No conversation list, filter, search hit or outline shows a project for a conversation whose stored project path can no longer be read.
|
|
48
|
+
- An anonymized transcript export refuses while any Codex project path cannot be read, rather than producing bytes that only look scrubbed. `cctally transcript export` exits 3 and the viewer says so; `--raw` is unaffected.
|
|
49
|
+
- An account-scoped anonymized Codex transcript now scrubs project paths tied to that account. If path ownership is uncertain, export and the viewer's Anon copy refuse instead of claiming the copy is anonymous.
|
|
50
|
+
- In the Conversation Viewer, a refused anonymized per-turn copy now shows a red X on the control, so a tap visibly reports that nothing was copied.
|
|
51
|
+
- The dashboard's Session detail no longer logs a failed outline request when a transcript was not kept. The Conversation Viewer names a temporarily unavailable transcript store on deep links and while search results load.
|
|
52
|
+
- `cctally explain` and the dashboard's Explain view retry a brief database change before reporting that the read could not complete.
|
|
53
|
+
|
|
8
54
|
## [1.109.0] - 2026-09-15
|
|
9
55
|
|
|
10
56
|
### Added
|
package/README.md
CHANGED
|
@@ -30,15 +30,15 @@ Your Claude Code plan meters you with a percentage that creeps up all week. ccta
|
|
|
30
30
|
</p>
|
|
31
31
|
|
|
32
32
|
<!-- cctally:latest-stable:begin -->
|
|
33
|
-
**Latest stable: v1.
|
|
33
|
+
**Latest stable: v1.109.0** (2026-09-15)
|
|
34
34
|
|
|
35
|
-
Highlights from the `v1.
|
|
35
|
+
Highlights from the `v1.104.0` to `v1.109.0` stable upgrade:
|
|
36
36
|
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
37
|
+
- The dashboard picks up a new pricing revision without a restart. It re-reads the deployed pricing file every 60 seconds, adopts a complete newer revision, and keeps serving; no connected client is dropped.
|
|
38
|
+
- The conversation filter popover says `Projects appear once indexing finishes.` in place of `No projects.` while the transcript index is being rebuilt. Model counts stay live on the Claude tab.
|
|
39
|
+
- The dashboard's Projects panel gives every billing cycle of a credited week its own percentage. Both cycles shared one calendar date, and only the later reading survived, so the second cycle could never carry one.
|
|
40
40
|
|
|
41
|
-
[See every change in this stable upgrade](https://github.com/omrikais/cctally/releases/tag/v1.
|
|
41
|
+
[See every change in this stable upgrade](https://github.com/omrikais/cctally/releases/tag/v1.109.0)
|
|
42
42
|
<!-- cctally:latest-stable:end -->
|
|
43
43
|
|
|
44
44
|
## Quick start
|
package/bin/_cctally_cache.py
CHANGED
|
@@ -136,6 +136,9 @@ import _lib_quota
|
|
|
136
136
|
# #496 S5b §4: the journal-to-cache coverage certificate kernel. Stdlib-only,
|
|
137
137
|
# so it is circular-safe for the same reason.
|
|
138
138
|
import _lib_cache_coverage
|
|
139
|
+
# #845 §4.1: the decode helper is a stdlib-only leaf that imports nothing from
|
|
140
|
+
# this package, so binding it here is circular-safe for the same reason.
|
|
141
|
+
import _lib_codex_metadata
|
|
139
142
|
|
|
140
143
|
|
|
141
144
|
# Module-level back-ref shims for the three out-of-scope JSONL/project
|
|
@@ -2766,18 +2769,45 @@ def _recompute_codex_rollups(
|
|
|
2766
2769
|
models = sorted({r.model for r in rows if r.model})
|
|
2767
2770
|
models_json = json.dumps(models) if models else None
|
|
2768
2771
|
source_root_key = rows[0].source_root_key
|
|
2772
|
+
# #845 §4.7. The two columns are read as BLOBs and decoded field by
|
|
2773
|
+
# field, so one undecodable byte no longer raises here and no longer
|
|
2774
|
+
# fails the whole recompute. The table name stays UNQUALIFIED, because
|
|
2775
|
+
# this writer must honor account scoping: under
|
|
2776
|
+
# `scope_conversations_db_to_account` it sees the empty TEMP view on
|
|
2777
|
+
# purpose.
|
|
2769
2778
|
thread = conn.execute(
|
|
2770
|
-
"SELECT cwd
|
|
2779
|
+
"SELECT CAST(cwd AS BLOB) AS cwd_blob, "
|
|
2780
|
+
"CAST(git_json AS BLOB) AS git_json_blob, parent_thread_id, "
|
|
2781
|
+
"source_root_key "
|
|
2771
2782
|
"FROM codex_conversation_threads WHERE conversation_key = ?",
|
|
2772
2783
|
(conversation_key,),
|
|
2773
2784
|
).fetchone()
|
|
2774
2785
|
cwd = git_json = parent_thread_id = None
|
|
2786
|
+
thread_malformed = False
|
|
2775
2787
|
if thread is not None:
|
|
2776
|
-
|
|
2788
|
+
cwd_blob, git_json_blob, parent_thread_id, thread_root = thread
|
|
2789
|
+
decoded = _lib_codex_metadata.decode_codex_project_metadata(
|
|
2790
|
+
cwd_blob, git_json_blob)
|
|
2791
|
+
cwd, git_json = decoded.cwd, decoded.git_json
|
|
2792
|
+
# The DIRECT-ONLY predicate: this writer resolves the thread's own
|
|
2793
|
+
# fields through `_codex_conversation_project_attribution`, which
|
|
2794
|
+
# stops at a usable `cwd`, so a valid `cwd` beside an undecodable
|
|
2795
|
+
# `git_json` is still attributed.
|
|
2796
|
+
thread_malformed = _lib_codex_metadata.codex_metadata_is_malformed(
|
|
2797
|
+
decoded, None)
|
|
2777
2798
|
if thread_root:
|
|
2778
2799
|
source_root_key = thread_root
|
|
2779
|
-
|
|
2780
|
-
|
|
2800
|
+
if thread_malformed:
|
|
2801
|
+
# `(unassigned)` is a real answer for a thread with no metadata.
|
|
2802
|
+
# Persisting it for a thread whose metadata could not be READ would
|
|
2803
|
+
# stamp a wrong answer that every later read prefers, which is the
|
|
2804
|
+
# failure class `docs/codex-gotchas.md` records for the materialized
|
|
2805
|
+
# `"(unassigned)"`. NULL is the absence every reader already
|
|
2806
|
+
# handles.
|
|
2807
|
+
project_key = project_label = None
|
|
2808
|
+
else:
|
|
2809
|
+
project_key, project_label = _codex_conversation_project_attribution(
|
|
2810
|
+
source_root_key, cwd, git_json)
|
|
2781
2811
|
conn.execute(
|
|
2782
2812
|
"INSERT INTO codex_conversation_rollups "
|
|
2783
2813
|
"(conversation_key, source_root_key, parent_thread_id, item_count, "
|
|
@@ -11012,23 +11042,42 @@ def scope_conversations_db_to_account(
|
|
|
11012
11042
|
}
|
|
11013
11043
|
safe_project_attribution: dict[str, tuple[str | None, str | None]] = {}
|
|
11014
11044
|
for conversation_key in codex_keys:
|
|
11045
|
+
# #845 §4.7. The thread is re-tested EVEN WHEN a rollup is persisted:
|
|
11046
|
+
# a rollup materialized before the thread was corrupted carries a
|
|
11047
|
+
# pre-corruption attribution, and preferring it would keep publishing
|
|
11048
|
+
# an answer the store can no longer support. The raw table is named
|
|
11049
|
+
# explicitly because the unqualified name is an empty TEMP view here.
|
|
11050
|
+
thread = conn.execute(
|
|
11051
|
+
"SELECT source_root_key, CAST(cwd AS BLOB) AS cwd_blob, "
|
|
11052
|
+
"CAST(git_json AS BLOB) AS git_json_blob "
|
|
11053
|
+
"FROM cache_db.codex_conversation_threads WHERE conversation_key=?",
|
|
11054
|
+
(conversation_key,),
|
|
11055
|
+
).fetchone()
|
|
11056
|
+
decoded = None
|
|
11057
|
+
thread_malformed = False
|
|
11058
|
+
if thread is not None:
|
|
11059
|
+
decoded = _lib_codex_metadata.decode_codex_project_metadata(
|
|
11060
|
+
thread[1], thread[2])
|
|
11061
|
+
thread_malformed = _lib_codex_metadata.codex_metadata_is_malformed(
|
|
11062
|
+
decoded, None)
|
|
11015
11063
|
persisted = conn.execute(
|
|
11016
11064
|
"SELECT project_key,project_label "
|
|
11017
11065
|
"FROM main.codex_conversation_rollups WHERE conversation_key=?",
|
|
11018
11066
|
(conversation_key,),
|
|
11019
11067
|
).fetchone()
|
|
11020
11068
|
if persisted is not None:
|
|
11021
|
-
safe_project_attribution[conversation_key] = persisted
|
|
11022
|
-
continue
|
|
11023
|
-
thread = conn.execute(
|
|
11024
|
-
"SELECT source_root_key,cwd,git_json "
|
|
11025
|
-
"FROM cache_db.codex_conversation_threads WHERE conversation_key=?",
|
|
11026
|
-
(conversation_key,),
|
|
11027
|
-
).fetchone()
|
|
11028
|
-
if thread is not None:
|
|
11029
11069
|
safe_project_attribution[conversation_key] = (
|
|
11030
|
-
|
|
11070
|
+
(None, None) if thread_malformed else persisted
|
|
11031
11071
|
)
|
|
11072
|
+
continue
|
|
11073
|
+
if thread is not None:
|
|
11074
|
+
if thread_malformed:
|
|
11075
|
+
safe_project_attribution[conversation_key] = (None, None)
|
|
11076
|
+
else:
|
|
11077
|
+
safe_project_attribution[conversation_key] = (
|
|
11078
|
+
_codex_conversation_project_attribution(
|
|
11079
|
+
thread[0], decoded.cwd, decoded.git_json)
|
|
11080
|
+
)
|
|
11032
11081
|
_recompute_codex_rollups(
|
|
11033
11082
|
conn, codex_keys, advance_render_revision=False,
|
|
11034
11083
|
)
|
package/bin/_cctally_core.py
CHANGED
|
@@ -1923,7 +1923,11 @@ def open_db(*, _target_path=None) -> sqlite3.Connection:
|
|
|
1923
1923
|
import importlib
|
|
1924
1924
|
_cctally_store = importlib.import_module("_cctally_store")
|
|
1925
1925
|
|
|
1926
|
-
|
|
1926
|
+
# Alternate targets are private scratch indexes. A rederive preview may
|
|
1927
|
+
# open one while the live data directory has never had a logs/ directory;
|
|
1928
|
+
# creating that directory would violate the preview's zero-write contract.
|
|
1929
|
+
if _target_path is None:
|
|
1930
|
+
ensure_dirs()
|
|
1927
1931
|
# #453: probe the live index before the maintenance-shared opener. During
|
|
1928
1932
|
# a large detached rebuild that opener can wait for its bounded 5-second
|
|
1929
1933
|
# guard; a statusline arriving mid-replay would otherwise pay that delay
|