cctally 1.44.1 → 1.44.3
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
CHANGED
|
@@ -5,6 +5,18 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.44.3] - 2026-06-15
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- `blocks` and the dashboard Blocks panel no longer split a single 5-hour window into two overlapping blocks when Anthropic's `rate_limits.5h.resets_at` jitters by one second across a 10-minute boundary (e.g. `20:39:59` vs `20:40:00`). The recorded-window loader identified each window by re-flooring the raw, jittery reset string to 10-minute buckets, so a 1-second straddle landed in two different buckets and rendered as two adjacent ~5h rows (with the bulk of the cost in the spurious *earlier* row, e.g. a phantom `6:30 p.m.` block alongside the real `6:40 p.m.` one); the weighted non-overlap scheduler then kept the phantom because its heavy canonical neighbor outweighed the real chain by a single point, and the #116 force-restore added the real anchor back without evicting the phantom. The loader now keys each window by the canonical, jitter-collapsed `five_hour_window_key` the `record-usage` path already stores — so a straddle collapses to one bucket and both the CLI and the dashboard show a single block with the correct total. The committed `five_hour_blocks` rollup was always correct; only this display path was affected, and it self-corrects on the next read with no migration (#201).
|
|
12
|
+
- **Internal (test infra, no user-facing change): the per-migration golden byte-idempotency guard (#197) now passes on the public Linux CI matrix.** That guard compared each rebuilt golden to the committed `.sqlite` byte-for-byte, normalizing only the 4-byte writer-version header — but SQLite's whole on-disk page layout (page allocation / freelist / B-tree balancing) is not portable across library versions, and FTS5 stores its inverted index as version-dependent binary segments, so the goldens (generated on the maintainer's macOS SQLite 3.53.2) byte-mismatched the public Linux CI's SQLite 3.45.1 even though their logical content is identical. v1.44.2 was the guard's first exposure to a non-macOS SQLite (the matrix only runs on tag push / weekly cron / dispatch), so it went red. The guard is now two-tier: STRICT byte-idempotency on the goldens' origin SQLite version (the maintainer's machine, the full #197 guarantee that a regen won't dirty the committed file), falling back to a version-portable SEMANTIC fingerprint — `user_version` / `application_id` plus the canonical SQL dump of every real table, taken from an in-memory copy with the FTS5 virtual tables dropped — on any other SQLite version. Still catches real builder/data drift (verified non-vacuous); nothing to do on upgrade (#199).
|
|
13
|
+
|
|
14
|
+
## [1.44.2] - 2026-06-14
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Conversation viewer: an `Edit`/`Write`/`MultiEdit` card whose input was truncated for transport now shows the document's **true** line count in its header (`wrote N lines` / `+A −D`) instead of the post-truncation count — previously a Write of a file larger than ~8 KB rendered e.g. `wrote 114 lines` (the clipped prefix) until you clicked "load full input", which then corrected it. The true `{add, del}` is now computed from the full input at ingest and stamped on truncated edit calls (matching the in-browser diff exactly: added rows = `|new lines| − LCS`, removed = `|old lines| − LCS`, with the unique LCS length), and the header prefers it only while the input is truncated-and-not-yet-loaded so a non-truncated card still mirrors its rendered diff. Already-cached conversations pick up the correct count after `cctally cache-sync --rebuild` (the cache is re-derivable); newly-recorded conversations are correct immediately (#198).
|
|
18
|
+
- **Internal (test infra, no user-facing change): the per-migration golden builders are now byte-idempotent, with a guard that enforces it.** The conversation cache goldens build `pre.sqlite` via `_apply_cache_schema`, which always emits the current full cache schema, so as later migrations added tables (`conversation_sessions` #013, `conversation_ai_titles` #012) or reshaped the FTS5 index (#010) the committed goldens silently fell behind — a full `bin/build-migrations-fixtures.py` regen rewrote ~24 fixtures at once that the maintainer then had to hand-revert (the broader cousin of #194, which fixed only the missing-marker case). Cache `001`'s wall-clock self-stamp (the #140 carve-out) is now overwritten with a pinned `applied_at_utc` in the builder (production handler untouched), removing the last source of regen non-determinism; all builder-produced goldens were refreshed to the current schema; and `tests/test_build_migrations_fixtures_stamps_markers.py` now rebuilds every per-migration golden (across both builder scripts) and asserts byte-equality with the committed fixture, so future schema drift fails loudly at the commit that introduces it instead of accumulating silently. Nothing to do on upgrade (#197).
|
|
19
|
+
|
|
8
20
|
## [1.44.1] - 2026-06-14
|
|
9
21
|
|
|
10
22
|
### Fixed
|
|
@@ -307,7 +307,7 @@ def _load_recorded_five_hour_windows(
|
|
|
307
307
|
try:
|
|
308
308
|
with open_db() as conn:
|
|
309
309
|
rows = conn.execute(
|
|
310
|
-
"SELECT five_hour_resets_at "
|
|
310
|
+
"SELECT five_hour_resets_at, five_hour_window_key "
|
|
311
311
|
"FROM weekly_usage_snapshots "
|
|
312
312
|
"WHERE five_hour_resets_at IS NOT NULL "
|
|
313
313
|
" AND five_hour_resets_at >= ? "
|
|
@@ -326,7 +326,8 @@ def _load_recorded_five_hour_windows(
|
|
|
326
326
|
canonical_rows: list[Any] = []
|
|
327
327
|
try:
|
|
328
328
|
canonical_rows = conn.execute(
|
|
329
|
-
"SELECT five_hour_resets_at, block_start_at "
|
|
329
|
+
"SELECT five_hour_resets_at, block_start_at, "
|
|
330
|
+
" five_hour_window_key "
|
|
330
331
|
"FROM five_hour_blocks "
|
|
331
332
|
"WHERE five_hour_resets_at IS NOT NULL "
|
|
332
333
|
" AND five_hour_resets_at >= ? "
|
|
@@ -378,9 +379,25 @@ def _load_recorded_five_hour_windows(
|
|
|
378
379
|
# denied on parent dir) that propagate from open_db() before any
|
|
379
380
|
# SQL runs. Either way, fall back to the heuristic anchor path.
|
|
380
381
|
return [], {}, {}
|
|
382
|
+
# issue #201: identify each 5h window by its canonical, jitter-collapsed
|
|
383
|
+
# ``five_hour_window_key`` (the 10-min-floored epoch the record path
|
|
384
|
+
# already stored via the anchored ``_canonical_5h_window_key`` reuse)
|
|
385
|
+
# instead of re-flooring the raw ``five_hour_resets_at`` string. A
|
|
386
|
+
# 1-second reset jitter straddling a 10-minute floor boundary
|
|
387
|
+
# (``20:39:59`` vs ``20:40:00``) floors to two different buckets and
|
|
388
|
+
# would otherwise fork one physical window into two overlapping blocks
|
|
389
|
+
# — the exact split this column exists to prevent. Falls back to the
|
|
390
|
+
# pure floor for legacy rows whose key wasn't backfilled (``open_db``
|
|
391
|
+
# backfills NULL keys before this query runs, so this is defensive).
|
|
392
|
+
def _bucket_dt(window_key: Any, resets_dt: dt.datetime) -> dt.datetime:
|
|
393
|
+
if window_key is not None:
|
|
394
|
+
return dt.datetime.fromtimestamp(int(window_key), dt.timezone.utc)
|
|
395
|
+
return _c._floor_to_ten_minutes(resets_dt)
|
|
396
|
+
|
|
381
397
|
counts: dict[dt.datetime, int] = {}
|
|
382
398
|
for row in rows:
|
|
383
399
|
raw = row["five_hour_resets_at"] if hasattr(row, "keys") else row[0]
|
|
400
|
+
wkey = row["five_hour_window_key"] if hasattr(row, "keys") else row[1]
|
|
384
401
|
if raw is None:
|
|
385
402
|
continue
|
|
386
403
|
try:
|
|
@@ -391,7 +408,7 @@ def _load_recorded_five_hour_windows(
|
|
|
391
408
|
d = d.replace(tzinfo=dt.timezone.utc)
|
|
392
409
|
else:
|
|
393
410
|
d = d.astimezone(dt.timezone.utc)
|
|
394
|
-
snapped =
|
|
411
|
+
snapped = _bucket_dt(wkey, d)
|
|
395
412
|
counts[snapped] = counts.get(snapped, 0) + 1
|
|
396
413
|
# Overlay canonical rollup anchors at heavy weight. Same flooring
|
|
397
414
|
# rule so a jittered raw value (e.g. 17:48Z) and its canonicalized
|
|
@@ -411,10 +428,16 @@ def _load_recorded_five_hour_windows(
|
|
|
411
428
|
# truncated R keeps both blocks visible — without this fix the
|
|
412
429
|
# earlier block's entries are silently rendered as a phantom
|
|
413
430
|
# heuristic "~" row by `_group_entries_into_blocks`.
|
|
414
|
-
|
|
431
|
+
# issue #201: each triple is ``(wkey_dt, bs, rs)`` — ``wkey_dt`` is the
|
|
432
|
+
# canonical window_key (decoded to a UTC datetime) and is the SAME
|
|
433
|
+
# bucket identity used for the raw-snapshot counts above, so the heavy
|
|
434
|
+
# canonical overlay always lands in the same bucket as its supporting
|
|
435
|
+
# raw rows (never a jitter-split sibling bucket).
|
|
436
|
+
canonical_pairs: list[tuple[dt.datetime, dt.datetime, dt.datetime]] = []
|
|
415
437
|
for row in canonical_rows:
|
|
416
438
|
rs_raw = row["five_hour_resets_at"] if hasattr(row, "keys") else row[0]
|
|
417
439
|
bs_raw = row["block_start_at"] if hasattr(row, "keys") else row[1]
|
|
440
|
+
wkey = row["five_hour_window_key"] if hasattr(row, "keys") else row[2]
|
|
418
441
|
if rs_raw is None or bs_raw is None:
|
|
419
442
|
continue
|
|
420
443
|
try:
|
|
@@ -430,8 +453,8 @@ def _load_recorded_five_hour_windows(
|
|
|
430
453
|
bs = bs.replace(tzinfo=dt.timezone.utc)
|
|
431
454
|
else:
|
|
432
455
|
bs = bs.astimezone(dt.timezone.utc)
|
|
433
|
-
canonical_pairs.append((bs, rs))
|
|
434
|
-
canonical_pairs.sort(key=lambda p: p[
|
|
456
|
+
canonical_pairs.append((_bucket_dt(wkey, rs), bs, rs))
|
|
457
|
+
canonical_pairs.sort(key=lambda p: p[1])
|
|
435
458
|
|
|
436
459
|
# issue #76: canonical_intervals maps every floored R -> its EXACT
|
|
437
460
|
# (block_start_at, five_hour_resets_at) — both UTC, rs un-floored
|
|
@@ -444,9 +467,8 @@ def _load_recorded_five_hour_windows(
|
|
|
444
467
|
canonical_intervals: dict[
|
|
445
468
|
dt.datetime, tuple[dt.datetime, dt.datetime]
|
|
446
469
|
] = {}
|
|
447
|
-
for bs, rs in canonical_pairs:
|
|
448
|
-
|
|
449
|
-
canonical_intervals[snapped] = (bs, rs)
|
|
470
|
+
for wkey_dt, bs, rs in canonical_pairs:
|
|
471
|
+
canonical_intervals[wkey_dt] = (bs, rs)
|
|
450
472
|
|
|
451
473
|
# Detect overlap-with-credit and replace the earlier R with a
|
|
452
474
|
# credit-truncated anchor. The (anchor → real_block_start) map is
|
|
@@ -454,11 +476,12 @@ def _load_recorded_five_hour_windows(
|
|
|
454
476
|
# real block_start_at on the display row (instead of the default
|
|
455
477
|
# R - 5h, which would be hours earlier for a 2h-truncated block).
|
|
456
478
|
block_start_overrides: dict[dt.datetime, dt.datetime] = {}
|
|
457
|
-
truncated_pairs: list[tuple[dt.datetime, dt.datetime]] = []
|
|
458
|
-
for i, (bs, rs) in enumerate(canonical_pairs):
|
|
479
|
+
truncated_pairs: list[tuple[dt.datetime, dt.datetime, dt.datetime]] = []
|
|
480
|
+
for i, (wkey_dt, bs, rs) in enumerate(canonical_pairs):
|
|
481
|
+
anchor_key = wkey_dt # canonical window_key identity (issue #201)
|
|
459
482
|
truncated_R = rs
|
|
460
483
|
if i + 1 < len(canonical_pairs):
|
|
461
|
-
next_bs, _next_rs = canonical_pairs[i + 1]
|
|
484
|
+
_next_wk, next_bs, _next_rs = canonical_pairs[i + 1]
|
|
462
485
|
if rs > next_bs: # overlap with next block
|
|
463
486
|
# Look for a credit moment inside [next_bs, rs] — the
|
|
464
487
|
# part of the earlier block that overlaps the next.
|
|
@@ -471,24 +494,24 @@ def _load_recorded_five_hour_windows(
|
|
|
471
494
|
# drop one via its weight-tiebreaker.
|
|
472
495
|
if bs < cm_floored < rs:
|
|
473
496
|
truncated_R = cm_floored
|
|
497
|
+
anchor_key = cm_floored
|
|
474
498
|
block_start_overrides[cm_floored] = bs
|
|
475
|
-
# Rewrite canonical_intervals
|
|
476
|
-
#
|
|
477
|
-
#
|
|
478
|
-
#
|
|
479
|
-
#
|
|
480
|
-
#
|
|
481
|
-
#
|
|
482
|
-
#
|
|
483
|
-
#
|
|
484
|
-
#
|
|
485
|
-
|
|
486
|
-
canonical_intervals.pop(snapped_orig, None)
|
|
499
|
+
# Rewrite canonical_intervals under the
|
|
500
|
+
# truncated key. issue #76: the partitioner
|
|
501
|
+
# reads canonical_intervals for the exact
|
|
502
|
+
# bs/rs; the truncated entry must reflect the
|
|
503
|
+
# credit-shifted upper bound (cm_floored) AND
|
|
504
|
+
# the real bs (the override) so partition +
|
|
505
|
+
# Phase 1.5 render the credit-shortened block
|
|
506
|
+
# consistently. issue #201: the original
|
|
507
|
+
# entry is keyed by the canonical window_key,
|
|
508
|
+
# so pop under ``wkey_dt`` (not floor(rs)).
|
|
509
|
+
canonical_intervals.pop(wkey_dt, None)
|
|
487
510
|
canonical_intervals[cm_floored] = (
|
|
488
511
|
bs, cm_floored,
|
|
489
512
|
)
|
|
490
513
|
break
|
|
491
|
-
truncated_pairs.append((bs, truncated_R))
|
|
514
|
+
truncated_pairs.append((anchor_key, bs, truncated_R))
|
|
492
515
|
|
|
493
516
|
# Truncated anchors are credit-adjusted and known-good; bypass the
|
|
494
517
|
# `_select_non_overlapping_recorded_windows` weighted scheduler for
|
|
@@ -501,16 +524,16 @@ def _load_recorded_five_hour_windows(
|
|
|
501
524
|
# input weight (so jittered same-bucket raw values still collapse)
|
|
502
525
|
# but skip them when computing the overlap-safe subset.
|
|
503
526
|
truncated_anchors: set[dt.datetime] = set()
|
|
504
|
-
for
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
# Identify truncated anchors by membership in the override
|
|
510
|
-
# (only credit-truncated entries land there).
|
|
511
|
-
if
|
|
512
|
-
truncated_anchors.add(
|
|
513
|
-
counts[
|
|
527
|
+
for anchor_key, _bs, _rs in truncated_pairs:
|
|
528
|
+
# ``anchor_key`` is already a floored canonical key — the window_key
|
|
529
|
+
# for an untruncated anchor (issue #201) or the credit-floored key
|
|
530
|
+
# for a truncated one — and the override map is keyed by it
|
|
531
|
+
# directly, so the legacy floor-relocation dance is no longer
|
|
532
|
+
# needed. Identify truncated anchors by membership in the override
|
|
533
|
+
# map (only credit-truncated entries land there).
|
|
534
|
+
if anchor_key in block_start_overrides:
|
|
535
|
+
truncated_anchors.add(anchor_key)
|
|
536
|
+
counts[anchor_key] = counts.get(anchor_key, 0) + _CANONICAL_WEIGHT_THRESHOLD
|
|
514
537
|
|
|
515
538
|
non_truncated_items = [
|
|
516
539
|
(a, w) for a, w in counts.items() if a not in truncated_anchors
|
package/bin/_lib_conversation.py
CHANGED
|
@@ -178,6 +178,12 @@ _INPUT_MAX_DEPTH = 12 # max nesting depth before subtree elision (Recursion
|
|
|
178
178
|
_INPUT_KEY_CAP = 512 # max chars per dict key (else keys are stored verbatim, unbounded)
|
|
179
179
|
_INPUT_ELISION = "…" # sentinel for elided leaves / subtrees
|
|
180
180
|
|
|
181
|
+
# #198: cap the O(n·m) LCS pass that stamps edit_stat from the FULL (unbounded)
|
|
182
|
+
# input. Realistic truncated edits have a few hundred lines per side (n·m ~ 1e4–1e5);
|
|
183
|
+
# this bound only excludes a pathological multi-thousand-line edit, in which case
|
|
184
|
+
# edit_stat is omitted and the client falls back to its bounded recompute.
|
|
185
|
+
_EDIT_STAT_LCS_CELL_BUDGET = 4_000_000
|
|
186
|
+
|
|
181
187
|
# #177 S4: WebSearch link-list capture bounds + the media item types whose
|
|
182
188
|
# placeholders the ordinal chokepoint (iter_media_items) addresses.
|
|
183
189
|
_WEB_SEARCH_LINK_CAP = 50
|
|
@@ -470,6 +476,14 @@ def _blocks_and_text(content):
|
|
|
470
476
|
"input": bounded, "input_truncated": input_trunc,
|
|
471
477
|
"id": b.get("id"),
|
|
472
478
|
"preview": tool_preview(b.get("name"), b.get("input"))}
|
|
479
|
+
# #198: stamp the true edit-family stat from the FULL input ONLY when
|
|
480
|
+
# the bounded copy was clipped — the one case where the client can't
|
|
481
|
+
# recount the header from `block["input"]`. Additive; omitted
|
|
482
|
+
# otherwise (non-truncated cards recount from their live jsdiff hunks).
|
|
483
|
+
if input_trunc:
|
|
484
|
+
edit_stat = _edit_stat_for(b.get("name"), b.get("input"))
|
|
485
|
+
if edit_stat is not None:
|
|
486
|
+
block["edit_stat"] = edit_stat
|
|
473
487
|
inp = b.get("input")
|
|
474
488
|
st = inp.get("subagent_type") if isinstance(inp, dict) else None
|
|
475
489
|
if isinstance(st, str) and st: # #166: spawn kind (Agent/Task)
|
|
@@ -760,6 +774,117 @@ def _summarize(inp):
|
|
|
760
774
|
return s[:200]
|
|
761
775
|
|
|
762
776
|
|
|
777
|
+
# ---------------------------------------------------------------------------
|
|
778
|
+
# #198: true edit-family stat, stamped at ingest from the FULL (un-bounded) input.
|
|
779
|
+
#
|
|
780
|
+
# DiffCard's header badge (`wrote N lines` for Write, `+A −D` for Edit/MultiEdit)
|
|
781
|
+
# was computed client-side from `call.input`, which `_bound_input` clips to
|
|
782
|
+
# _INPUT_LEAF_CAP per string leaf — so a large Write/Edit reported the post-clip
|
|
783
|
+
# count, not the document's true total. We stamp the true {add, del} here (where
|
|
784
|
+
# the full input is still in hand) on TRUNCATED edit-family calls only; the client
|
|
785
|
+
# prefers it for the header solely while truncated-and-not-yet-loaded, so a
|
|
786
|
+
# non-truncated card keeps header==body parity with its rendered jsdiff hunks.
|
|
787
|
+
#
|
|
788
|
+
# Parity with the client's jsdiff (dashboard/web/src/conversations/computeDiff.ts):
|
|
789
|
+
# jsdiff `diffLines` is Myers-minimal, so added rows = |new_lines| − LCS and
|
|
790
|
+
# removed = |old_lines| − LCS. The LCS *length* is unique, so a plain LCS pass
|
|
791
|
+
# reproduces jsdiff's counts WITHOUT replicating its alignment. Line tokens carry
|
|
792
|
+
# their trailing newline (jsdiff's tokenization), so a no-newline-at-eof line is a
|
|
793
|
+
# distinct token from its newline-terminated twin — matching the rendered diff.
|
|
794
|
+
# ---------------------------------------------------------------------------
|
|
795
|
+
def _line_count(s):
|
|
796
|
+
"""Number of lines in `s`, matching computeDiff.ts::splitLines length (split on
|
|
797
|
+
'\\n', drop the phantom blank from a trailing newline). Drives Write's
|
|
798
|
+
`wrote N lines`, which the client builds from `computeWrite(content).length`."""
|
|
799
|
+
if not s:
|
|
800
|
+
return 0
|
|
801
|
+
parts = s.split("\n")
|
|
802
|
+
if parts and parts[-1] == "":
|
|
803
|
+
parts.pop()
|
|
804
|
+
return len(parts)
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
def _line_tokens(s):
|
|
808
|
+
"""Tokenize `s` into jsdiff line tokens (each line keeps its trailing newline),
|
|
809
|
+
mirroring jsdiff's LineDiff.tokenize so the LCS below counts what `diffLines`
|
|
810
|
+
counts. `re.split(r"(\\n|\\r\\n)")` keeps separators; the trailing empty from a
|
|
811
|
+
final newline is dropped, then each separator is folded onto its line."""
|
|
812
|
+
if not s:
|
|
813
|
+
return []
|
|
814
|
+
parts = re.split(r"(\n|\r\n)", s)
|
|
815
|
+
if parts and parts[-1] == "":
|
|
816
|
+
parts.pop()
|
|
817
|
+
tokens = []
|
|
818
|
+
for i, p in enumerate(parts):
|
|
819
|
+
if i % 2: # captured separator → fold onto the preceding line
|
|
820
|
+
tokens[-1] += p
|
|
821
|
+
else:
|
|
822
|
+
tokens.append(p)
|
|
823
|
+
return tokens
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
def _lcs_len(a, b):
|
|
827
|
+
"""Length of the longest common subsequence of token lists `a`, `b` (rolling
|
|
828
|
+
1-D DP, O(len(a)·len(b)) time / O(min) space). The length is unique even when
|
|
829
|
+
the LCS itself is not, which is exactly why it reproduces jsdiff's add/del
|
|
830
|
+
counts."""
|
|
831
|
+
if not a or not b:
|
|
832
|
+
return 0
|
|
833
|
+
if len(b) > len(a):
|
|
834
|
+
a, b = b, a # keep the inner row short
|
|
835
|
+
prev = [0] * (len(b) + 1)
|
|
836
|
+
for x in a:
|
|
837
|
+
diag = 0 # prev[j-1] before this row overwrote it
|
|
838
|
+
for j in range(1, len(b) + 1):
|
|
839
|
+
cur = prev[j]
|
|
840
|
+
prev[j] = diag + 1 if x == b[j - 1] else (prev[j] if prev[j] >= prev[j - 1] else prev[j - 1])
|
|
841
|
+
diag = cur
|
|
842
|
+
return prev[len(b)]
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
def _diff_stat(old, new):
|
|
846
|
+
"""{"add", "del"} for a single old→new line diff, or None when the LCS would
|
|
847
|
+
exceed the cell budget. Non-string sides coerce to '' (mirroring
|
|
848
|
+
computeMultiEdit's leaf coercion)."""
|
|
849
|
+
ot = _line_tokens(old if isinstance(old, str) else "")
|
|
850
|
+
nt = _line_tokens(new if isinstance(new, str) else "")
|
|
851
|
+
if len(ot) * len(nt) > _EDIT_STAT_LCS_CELL_BUDGET:
|
|
852
|
+
return None
|
|
853
|
+
lcs = _lcs_len(ot, nt)
|
|
854
|
+
return {"add": len(nt) - lcs, "del": len(ot) - lcs}
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
def _edit_stat_for(name, inp):
|
|
858
|
+
"""True {"add", "del"} for an edit-family tool computed from its FULL input, or
|
|
859
|
+
None when not an edit-family tool / not computable / over the LCS budget. Write
|
|
860
|
+
is a pure line count (no prior content); Edit diffs old→new; MultiEdit sums per
|
|
861
|
+
edit. Mirrors computeWrite/computeDiff/computeMultiEdit COUNTS exactly."""
|
|
862
|
+
if not isinstance(inp, dict):
|
|
863
|
+
return None
|
|
864
|
+
nm = (name or "").lower()
|
|
865
|
+
if nm == "write":
|
|
866
|
+
content = inp.get("content")
|
|
867
|
+
if not isinstance(content, str):
|
|
868
|
+
return None
|
|
869
|
+
return {"add": _line_count(content), "del": 0}
|
|
870
|
+
if nm == "edit":
|
|
871
|
+
return _diff_stat(inp.get("old_string"), inp.get("new_string"))
|
|
872
|
+
if nm == "multiedit":
|
|
873
|
+
edits = inp.get("edits")
|
|
874
|
+
if not isinstance(edits, list):
|
|
875
|
+
return None
|
|
876
|
+
add = dele = 0
|
|
877
|
+
for e in edits:
|
|
878
|
+
e = e if isinstance(e, dict) else {}
|
|
879
|
+
st = _diff_stat(e.get("old_string"), e.get("new_string"))
|
|
880
|
+
if st is None:
|
|
881
|
+
return None # one over-budget edit → omit the whole stamp
|
|
882
|
+
add += st["add"]
|
|
883
|
+
dele += st["del"]
|
|
884
|
+
return {"add": add, "del": dele}
|
|
885
|
+
return None
|
|
886
|
+
|
|
887
|
+
|
|
763
888
|
def _bound_input(inp):
|
|
764
889
|
"""Return (bounded_structured_input, truncated) for a tool_use input dict, or
|
|
765
890
|
(None, False) for a non-dict (the same non-dict contract as _summarize /
|
|
@@ -56,8 +56,8 @@ Error generating stack: `+e.message+`
|
|
|
56
56
|
`)&&(e=e.slice(0,-1)),t.endsWith(`
|
|
57
57
|
`)&&(t=t.slice(0,-1))),super.equals(e,t,n)}};function SS(e,t,n){return xS.diff(e,t,n)}function CS(e,t){t.stripTrailingCr&&(e=e.replace(/\r\n/g,`
|
|
58
58
|
`));let n=[],r=e.split(/(\n|\r\n)/);r[r.length-1]||r.pop();for(let e=0;e<r.length;e++){let i=r[e];e%2&&!t.newlineIsToken?n[n.length-1]+=i:n.push(i)}return n}function wS(e){return e==`.`||e==`!`||e==`?`}new class extends rS{tokenize(e){let t=[],n=0;for(let r=0;r<e.length;r++){if(r==e.length-1){t.push(e.slice(n));break}if(wS(e[r])&&e[r+1].match(/\s/)){for(t.push(e.slice(n,r+1)),r=n=r+1;e[r+1]?.match(/\s/);)r++;t.push(e.slice(n,r+1)),n=r+1}}return t}},new class extends rS{tokenize(e){return e.split(/([{}:;,]|\s+)/)}},new class extends rS{constructor(){super(...arguments),this.tokenize=CS}get useLongestToken(){return!0}castInput(e,t){let{undefinedReplacement:n,stringifyReplacer:r=(e,t)=>t===void 0?n:t}=t;return typeof e==`string`?e:JSON.stringify(TS(e,null,null,r),null,` `)}equals(e,t,n){return super.equals(e.replace(/,([\r\n])/g,`$1`),t.replace(/,([\r\n])/g,`$1`),n)}};function TS(e,t,n,r,i){t||=[],n||=[],r&&(e=r(i===void 0?``:i,e));let a;for(a=0;a<t.length;a+=1)if(t[a]===e)return n[a];let o;if(Object.prototype.toString.call(e)===`[object Array]`){for(t.push(e),o=Array(e.length),n.push(o),a=0;a<e.length;a+=1)o[a]=TS(e[a],t,n,r,String(a));return t.pop(),n.pop(),o}if(e&&e.toJSON&&(e=e.toJSON()),typeof e==`object`&&e){t.push(e),o={},n.push(o);let i=[],s;for(s in e)Object.prototype.hasOwnProperty.call(e,s)&&i.push(s);for(i.sort(),a=0;a<i.length;a+=1)s=i[a],o[s]=TS(e[s],t,n,r,s);t.pop(),n.pop()}else o=e;return o}new class extends rS{tokenize(e){return e.slice()}join(e){return e}removeEmpty(e){return e}};function ES(e){let t=e.split(`
|
|
59
|
-
`);return t.length&&t[t.length-1]===``&&t.pop(),t}function DS(e,t){let n=[],r=1,i=1;for(let a of SS(e,t))for(let e of ES(a.value))a.added?n.push({type:`add`,oldNo:null,newNo:i++,text:e}):a.removed?n.push({type:`del`,oldNo:r++,newNo:null,text:e}):n.push({type:`context`,oldNo:r++,newNo:i++,text:e});return OS(n)}function OS(e){for(let t=0;t<e.length;){if(e[t].type!==`del`){t++;continue}let n=t;for(;n<e.length&&e[n].type===`del`;)n++;let r=n;for(;r<e.length&&e[r].type===`add`;)r++;let i=e.slice(t,n),a=e.slice(n,r),o=Math.min(i.length,a.length);for(let e=0;e<o;e++){let t=bS(i[e].text,a[e].text);i[e].segments=t.filter(e=>!e.added).map(e=>({text:e.value,emph:!!e.removed})),a[e].segments=t.filter(e=>!e.removed).map(e=>({text:e.value,emph:!!e.added}))}t=r}return e}function kS(e){return ES(e).map((e,t)=>({type:`add`,oldNo:null,newNo:t+1,text:e}))}function AS(e){return Array.isArray(e)?e.map(e=>DS(typeof e?.old_string==`string`?e.old_string:``,typeof e?.new_string==`string`?e.new_string:``)):[]}function jS(e){return e.input??{}}function MS(e){return typeof e.file_path==`string`?e.file_path:``}function NS(e){let t=e.lastIndexOf(`/`);return t<0?{dir:``,base:e}:{dir:e.slice(0,t+1),base:e.slice(t+1)}}function PS(e){let t=0,n=0;for(let r of e)for(let e of r)e.type===`add`?t++:e.type===`del`&&n++;return{add:t,del:n}}function FS(e,t){let n=(e.name??``).toLowerCase(),r=t??jS(e);return n===`write`?{hunks:[kS(typeof r.content==`string`?r.content:``)],kind:`write`}:n===`multiedit`?{hunks:AS(Array.isArray(r.edits)?r.edits:[]),kind:`multiedit`}:{hunks:[DS(typeof r.old_string==`string`?r.old_string:``,typeof r.new_string==`string`?r.new_string:``)],kind:`edit`}}function IS({row:e,lang:t}){let n=e.type===`add`?`+`:e.type===`del`?`−`:`\xA0`,r;return r=e.type===`context`?Sx(e.text,t):e.segments?e.segments.map((e,t)=>e.emph?(0,U.jsx)(`span`,{className:`conv-diff-word`,children:e.text},t):(0,U.jsx)(`span`,{children:e.text},t)):e.text,(0,U.jsxs)(`div`,{className:`conv-diff-row conv-diff-row--${e.type}`,children:[(0,U.jsx)(`span`,{className:`conv-diff-gutter`,"aria-hidden":`true`,children:e.oldNo??``}),(0,U.jsx)(`span`,{className:`conv-diff-gutter`,"aria-hidden":`true`,children:e.newNo??``}),(0,U.jsx)(`span`,{className:`conv-diff-sign`,"aria-hidden":`true`,children:n}),(0,U.jsx)(`span`,{className:`conv-diff-text`,children:r})]})}function LS({rows:e,lang:t}){return(0,U.jsx)(`div`,{className:`conv-diff-hunk`,children:e.map((e,n)=>(0,U.jsx)(IS,{row:e,lang:t},n))})}function RS({call:e}){let[t,n]=(0,_.useState)(null),r=jS(e),{dir:i,base:a}=NS(MS(r)),o=Fx(e),{hunks:s,kind:c}=(0,_.useMemo)(()=>FS(e,t),[e,t]),l=(0,_.useMemo)(()=>PS(s),[s]),u=r.replace_all===!0,
|
|
60
|
-
`),[s]);return(0,U.jsxs)(`details`,{className:`conv-chip conv-chip--tool conv-diff-card`,open:!0,children:[(0,U.jsxs)(`summary`,{children:[(0,U.jsx)(`span`,{className:`conv-chev`,"aria-hidden":`true`}),(0,U.jsx)(Yb,{}),(0,U.jsx)(`span`,{className:`conv-chip-name`,children:e.name??`Edit`}),(0,U.jsxs)(`span`,{className:`conv-diff-hdr`,children:[(0,U.jsx)(`span`,{className:`conv-diff-base`,children:a||`(file)`}),i&&(0,U.jsx)(`span`,{className:`conv-diff-dir`,children:i}),c===`write`?(0,U.jsxs)(`span`,{className:`conv-diff-stat conv-diff-stat--write`,children:[`wrote `,
|
|
59
|
+
`);return t.length&&t[t.length-1]===``&&t.pop(),t}function DS(e,t){let n=[],r=1,i=1;for(let a of SS(e,t))for(let e of ES(a.value))a.added?n.push({type:`add`,oldNo:null,newNo:i++,text:e}):a.removed?n.push({type:`del`,oldNo:r++,newNo:null,text:e}):n.push({type:`context`,oldNo:r++,newNo:i++,text:e});return OS(n)}function OS(e){for(let t=0;t<e.length;){if(e[t].type!==`del`){t++;continue}let n=t;for(;n<e.length&&e[n].type===`del`;)n++;let r=n;for(;r<e.length&&e[r].type===`add`;)r++;let i=e.slice(t,n),a=e.slice(n,r),o=Math.min(i.length,a.length);for(let e=0;e<o;e++){let t=bS(i[e].text,a[e].text);i[e].segments=t.filter(e=>!e.added).map(e=>({text:e.value,emph:!!e.removed})),a[e].segments=t.filter(e=>!e.removed).map(e=>({text:e.value,emph:!!e.added}))}t=r}return e}function kS(e){return ES(e).map((e,t)=>({type:`add`,oldNo:null,newNo:t+1,text:e}))}function AS(e){return Array.isArray(e)?e.map(e=>DS(typeof e?.old_string==`string`?e.old_string:``,typeof e?.new_string==`string`?e.new_string:``)):[]}function jS(e){return e.input??{}}function MS(e){return typeof e.file_path==`string`?e.file_path:``}function NS(e){let t=e.lastIndexOf(`/`);return t<0?{dir:``,base:e}:{dir:e.slice(0,t+1),base:e.slice(t+1)}}function PS(e){let t=0,n=0;for(let r of e)for(let e of r)e.type===`add`?t++:e.type===`del`&&n++;return{add:t,del:n}}function FS(e,t){let n=(e.name??``).toLowerCase(),r=t??jS(e);return n===`write`?{hunks:[kS(typeof r.content==`string`?r.content:``)],kind:`write`}:n===`multiedit`?{hunks:AS(Array.isArray(r.edits)?r.edits:[]),kind:`multiedit`}:{hunks:[DS(typeof r.old_string==`string`?r.old_string:``,typeof r.new_string==`string`?r.new_string:``)],kind:`edit`}}function IS({row:e,lang:t}){let n=e.type===`add`?`+`:e.type===`del`?`−`:`\xA0`,r;return r=e.type===`context`?Sx(e.text,t):e.segments?e.segments.map((e,t)=>e.emph?(0,U.jsx)(`span`,{className:`conv-diff-word`,children:e.text},t):(0,U.jsx)(`span`,{children:e.text},t)):e.text,(0,U.jsxs)(`div`,{className:`conv-diff-row conv-diff-row--${e.type}`,children:[(0,U.jsx)(`span`,{className:`conv-diff-gutter`,"aria-hidden":`true`,children:e.oldNo??``}),(0,U.jsx)(`span`,{className:`conv-diff-gutter`,"aria-hidden":`true`,children:e.newNo??``}),(0,U.jsx)(`span`,{className:`conv-diff-sign`,"aria-hidden":`true`,children:n}),(0,U.jsx)(`span`,{className:`conv-diff-text`,children:r})]})}function LS({rows:e,lang:t}){return(0,U.jsx)(`div`,{className:`conv-diff-hunk`,children:e.map((e,n)=>(0,U.jsx)(IS,{row:e,lang:t},n))})}function RS({call:e}){let[t,n]=(0,_.useState)(null),r=jS(e),{dir:i,base:a}=NS(MS(r)),o=Fx(e),{hunks:s,kind:c}=(0,_.useMemo)(()=>FS(e,t),[e,t]),l=(0,_.useMemo)(()=>PS(s),[s]),u=e.edit_stat&&typeof e.edit_stat.add==`number`&&typeof e.edit_stat.del==`number`?e.edit_stat:null,d=e.input_truncated&&!t&&u?u:l,f=r.replace_all===!0,p=c===`multiedit`?s.length:0,m=(0,_.useMemo)(()=>s.flatMap(e=>e.map(e=>(e.type===`add`?`+`:e.type===`del`?`-`:` `)+e.text)).join(`
|
|
60
|
+
`),[s]);return(0,U.jsxs)(`details`,{className:`conv-chip conv-chip--tool conv-diff-card`,open:!0,children:[(0,U.jsxs)(`summary`,{children:[(0,U.jsx)(`span`,{className:`conv-chev`,"aria-hidden":`true`}),(0,U.jsx)(Yb,{}),(0,U.jsx)(`span`,{className:`conv-chip-name`,children:e.name??`Edit`}),(0,U.jsxs)(`span`,{className:`conv-diff-hdr`,children:[(0,U.jsx)(`span`,{className:`conv-diff-base`,children:a||`(file)`}),i&&(0,U.jsx)(`span`,{className:`conv-diff-dir`,children:i}),c===`write`?(0,U.jsxs)(`span`,{className:`conv-diff-stat conv-diff-stat--write`,children:[`wrote `,d.add,` lines`]}):(0,U.jsxs)(`span`,{className:`conv-diff-stat`,children:[(0,U.jsxs)(`span`,{className:`conv-diff-stat-add`,children:[`+`,d.add]}),` `,(0,U.jsxs)(`span`,{className:`conv-diff-stat-del`,children:[`−`,d.del]})]}),f&&(0,U.jsx)(`span`,{className:`conv-diff-tag`,children:`replace all`}),p>0&&(0,U.jsxs)(`span`,{className:`conv-diff-tag`,children:[p,` edit`,p===1?``:`s`]})]})]}),(0,U.jsxs)(`div`,{className:`conv-diff-body`,children:[(0,U.jsx)(`div`,{className:`conv-diff-copy`,children:(0,U.jsx)(yx,{text:m})}),s.map((e,t)=>(0,U.jsxs)(`div`,{children:[c===`multiedit`&&(0,U.jsxs)(`div`,{className:`conv-diff-divider`,children:[`edit `,t+1,` of `,s.length]}),(0,U.jsx)(LS,{rows:e,lang:o})]},t)),e.input_truncated&&(0,U.jsx)(nS,{toolUseId:e.tool_use_id??``,which:`input`,fullLength:null,label:`load full input`,onLoaded:e=>{e.which===`input`&&n(e.input)}}),e.result&&(0,U.jsxs)(`details`,{className:`conv-chip conv-chip--result conv-diff-result`,children:[(0,U.jsxs)(`summary`,{children:[(0,U.jsx)(`span`,{className:`conv-chev`,"aria-hidden":`true`}),(0,U.jsx)(`span`,{className:`conv-chip-name`,children:`result`}),(0,U.jsx)(`span`,{className:`conv-chip-preview`,children:`cat -n snippet`})]}),(0,U.jsxs)(`div`,{className:`conv-chip-body conv-tool-io`,children:[(0,U.jsx)(yx,{text:e.result.text}),(0,U.jsx)(jx,{code:e.result.text,lang:o})]})]})]})]})}var zS={30:`ansi-blk`,31:`ansi-red`,32:`ansi-grn`,33:`ansi-yel`,34:`ansi-blu`,35:`ansi-mag`,36:`ansi-cyn`,37:`ansi-wht`,90:`ansi-dim`,91:`ansi-red`,92:`ansi-grn`,93:`ansi-yel`,94:`ansi-blu`,95:`ansi-mag`,96:`ansi-cyn`,97:`ansi-wht`},BS=/\x1b\[([0-9;]*)m/g,VS=/\x1b\[[0-9;]*[A-Za-z]/g,HS=/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?/g,US=/\x1b/g;function WS(e){let t=[],n=0,r=null,i;for(BS.lastIndex=0;(i=BS.exec(e))!==null;){i.index>n&&t.push({text:e.slice(n,i.index),cls:r});let a=i[1].split(`;`).filter(Boolean).map(Number);(a.length===0||a.includes(0))&&(r=null);for(let e of a)zS[e]&&(r=zS[e]);n=BS.lastIndex}return n<e.length&&t.push({text:e.slice(n),cls:r}),t.map(e=>({...e,text:e.text.replace(VS,``).replace(HS,``).replace(US,``)})).filter(e=>e.text.length>0)}function GS({text:e}){return(0,U.jsx)(U.Fragment,{children:WS(e).map((e,t)=>e.cls?(0,U.jsx)(`span`,{className:e.cls,children:e.text},t):(0,U.jsx)(`span`,{children:e.text},t))})}function KS(e){let t=e.input?.command;return typeof t==`string`?t:``}function qS(e){let t=e.input?.description,n=typeof t==`string`?t.trim():``;return n.length>0?n:void 0}function JS(e,t){return typeof t==`string`&&t.length>0&&e.endsWith(t)?{stdout:e.slice(0,e.length-t.length),stderr:t}:{stdout:e,stderr:null}}function YS({call:e}){let[t,n]=(0,_.useState)(null),r=KS(e),i=e.result,a=i?.is_error===!0,o=e.interrupted===!0,s=t?.text??i?.text??``,{stdout:c,stderr:l}=t==null?JS(s,e.stderr):JS(s,t.stderr),u=a?(0,U.jsx)(`span`,{className:`conv-term-badge conv-term-badge--err`,children:`● error`}):o?(0,U.jsx)(`span`,{className:`conv-term-badge conv-term-badge--int`,children:`■ interrupted`}):null;return(0,U.jsxs)(`details`,{className:`conv-chip conv-chip--tool conv-term`,open:!0,children:[(0,U.jsxs)(`summary`,{children:[(0,U.jsx)(`span`,{className:`conv-chev`,"aria-hidden":`true`}),(0,U.jsx)(Xb,{}),(0,U.jsx)(`span`,{className:`conv-chip-name`,children:`Bash`}),(0,U.jsx)(`span`,{className:`conv-chip-preview`,children:qS(e)??e.preview}),u]}),(0,U.jsxs)(`div`,{className:`conv-term-body`,children:[(0,U.jsx)(`div`,{className:`conv-term-copy`,children:(0,U.jsx)(yx,{text:r})}),(0,U.jsxs)(`pre`,{className:`conv-term-cmd conv-code--hl`,children:[(0,U.jsxs)(`span`,{className:`conv-term-prompt`,"aria-hidden":`true`,children:[`$`,` `]}),Sx(r,`bash`)]}),i&&(0,U.jsxs)(U.Fragment,{children:[(c.length>0||!o&&l==null)&&(0,U.jsx)(`pre`,{className:`conv-term-out`,children:(0,U.jsx)(GS,{text:c})}),l!=null&&(0,U.jsx)(`pre`,{className:`conv-term-stderr`,children:(0,U.jsx)(GS,{text:l})}),i.truncated&&t==null&&(0,U.jsx)(nS,{toolUseId:e.tool_use_id??``,which:`result`,fullLength:i.full_length??null,label:`load full output`,onLoaded:e=>{e.which===`result`&&n({text:e.text,stderr:e.stderr})}})]})]})]})}function XS(e){let t=Math.floor(e*3/4);return t>=1024*1024?`~${(t/(1024*1024)).toFixed(1)} MB`:t>=1024?`~${Math.round(t/1024)} KB`:`~${t} B`}function ZS({media:e,toolUseId:t,uuid:n,context:r}){let i=$x(),[a,o]=(0,_.useState)(!1),s=t?`tool_use_id=${encodeURIComponent(t)}`:n?`uuid=${encodeURIComponent(n)}`:null;if(!(i!=null&&s!=null&&Number.isInteger(e.index)&&e.index>=0)||a)return(0,U.jsxs)(`span`,{className:`conv-chip conv-chip--media`,children:[e.kind===`image`?(0,U.jsx)(tx,{}):(0,U.jsx)(nx,{}),` `,e.media_type??e.kind,` · `,e.bytes,` B`,a&&(0,U.jsx)(`span`,{className:`conv-media-gone`,children:` · source no longer available`})]});let c=`/api/conversation/${encodeURIComponent(i)}/media?${s}&index=${e.index}`;return e.kind===`document`?(0,U.jsxs)(`span`,{className:`conv-chip conv-chip--media`,children:[(0,U.jsx)(nx,{}),` `,e.media_type??`document`,` · `,XS(e.bytes),` ·`,` `,(0,U.jsx)(`a`,{href:c,target:`_blank`,rel:`noopener noreferrer`,children:`open ↗`})]}):(0,U.jsxs)(`figure`,{className:`conv-media-figure`,children:[(0,U.jsx)(`img`,{src:c,loading:`lazy`,decoding:`async`,alt:`${r} image ${e.index+1} (${e.media_type??`image`})`,onError:()=>o(!0)}),(0,U.jsxs)(`figcaption`,{className:`conv-media-caption`,children:[(0,U.jsx)(`span`,{children:e.media_type??`image`}),(0,U.jsx)(`span`,{children:XS(e.bytes)}),(0,U.jsx)(`a`,{href:c,target:`_blank`,rel:`noopener noreferrer`,children:`open full size ↗`})]})]})}function QS(e){try{return new URL(e).hostname}catch{return``}}function $S(e){return/^https?:\/\//i.test(e)}function eC(e){let t=e.input?.url;return typeof t==`string`?t:``}function tC(e){let t=e.input?.prompt;return typeof t==`string`?t:``}function nC(e){return e.split(`
|
|
61
61
|
`).length>24||e.length>1400}function rC({call:e}){let t=eC(e),n=tC(e),r=QS(t),[i,a]=(0,_.useState)(!1),[o,s]=(0,_.useState)(null),c=o??e.result?.text??``,l=nC(c)&&!i,u=e.web_fetch,d=u!=null&&u.code>=200&&u.code<400;return(0,U.jsxs)(`details`,{className:`conv-chip conv-web`,open:!0,children:[(0,U.jsxs)(`summary`,{children:[(0,U.jsx)(`span`,{className:`conv-chev`,"aria-hidden":`true`}),(0,U.jsx)(Zb,{}),(0,U.jsx)(`span`,{className:`conv-chip-name`,children:`WebFetch`}),r&&(0,U.jsx)(`span`,{className:`conv-web-domain`,children:r}),u!=null&&(0,U.jsxs)(`span`,{className:`conv-web-status ${d?`conv-web-status--ok`:`conv-web-status--err`}`,children:[u.code,u.code_text?` ${u.code_text}`:``]}),e.result?.is_error&&(0,U.jsx)(`span`,{className:`conv-chip-status`,children:` · error`})]}),(0,U.jsxs)(`div`,{className:`conv-web-body`,children:[(0,U.jsxs)(`div`,{className:`conv-web-field`,children:[(0,U.jsx)(`span`,{className:`conv-web-key`,children:`url`}),$S(t)?(0,U.jsx)(`a`,{href:t,target:`_blank`,rel:`noopener noreferrer`,children:t}):(0,U.jsx)(`span`,{children:t})]}),n&&(0,U.jsxs)(`div`,{className:`conv-web-field`,children:[(0,U.jsx)(`span`,{className:`conv-web-key`,children:`prompt`}),(0,U.jsx)(`span`,{children:n})]}),c?(0,U.jsxs)(U.Fragment,{children:[(0,U.jsx)(`div`,{className:`conv-web-copy`,children:(0,U.jsx)(yx,{text:c})}),(0,U.jsx)(`div`,{className:`conv-web-md`+(l?` conv-web-md--clamp`:``),children:(0,U.jsx)(kx,{children:c})}),l&&(0,U.jsx)(`div`,{className:`conv-web-more`,children:(0,U.jsx)(`button`,{type:`button`,onClick:()=>a(!0),children:`Show full summary ↓`})})]}):e.result==null&&(0,U.jsx)(`div`,{className:`conv-tool-io-label conv-tool-io-label--none`,children:`no result`}),e.result?.media?.map(t=>(0,U.jsx)(ZS,{media:t,toolUseId:e.tool_use_id,context:`WebFetch`},t.index)),e.result?.truncated&&o==null&&e.tool_use_id&&(0,U.jsx)(nS,{toolUseId:e.tool_use_id,which:`result`,fullLength:e.result.full_length??null,label:`load full summary`,onLoaded:e=>{e.which===`result`&&s(e.text)}})]})]})}var iC=10;function aC(e){let t=e.input?.query;return typeof t==`string`?t:``}function oC({call:e}){let t=aC(e),n=e.web_search?.links,[r,i]=(0,_.useState)(!1),a=n!=null&&n.length>0,o=a&&!r?n.slice(0,iC):n??[];return(0,U.jsxs)(`details`,{className:`conv-chip conv-web`,open:!0,children:[(0,U.jsxs)(`summary`,{children:[(0,U.jsx)(`span`,{className:`conv-chev`,"aria-hidden":`true`}),(0,U.jsx)(gx,{}),(0,U.jsx)(`span`,{className:`conv-chip-name`,children:`WebSearch`}),(0,U.jsxs)(`span`,{className:`conv-web-domain`,children:[`“`,t,`”`]}),n!=null&&(0,U.jsxs)(`span`,{className:`conv-web-status conv-web-status--ok`,children:[n.length,e.web_search?.links_truncated?`+`:``,` results`]}),e.result?.is_error&&(0,U.jsx)(`span`,{className:`conv-chip-status`,children:` · error`})]}),(0,U.jsx)(`div`,{className:`conv-web-body`,children:a?(0,U.jsxs)(U.Fragment,{children:[o.map((e,t)=>(0,U.jsxs)(`div`,{className:`conv-web-link`,children:[$S(e.url)?(0,U.jsx)(`a`,{href:e.url,target:`_blank`,rel:`noopener noreferrer`,children:e.title}):(0,U.jsx)(`span`,{children:e.title}),(0,U.jsx)(`span`,{className:`conv-web-link-domain`,children:QS(e.url)||e.url})]},t)),!r&&n.length>iC&&(0,U.jsx)(`div`,{className:`conv-web-more`,children:(0,U.jsxs)(`button`,{type:`button`,onClick:()=>i(!0),children:[`+ `,n.length-iC,` more results`]})})]}):e.result?.text?(0,U.jsxs)(`div`,{className:`conv-tool-io`,children:[(0,U.jsx)(yx,{text:e.result.text}),(0,U.jsx)(`pre`,{className:`conv-code conv-code--result`,children:e.result.text})]}):(0,U.jsx)(`div`,{className:`conv-tool-io-label conv-tool-io-label--none`,children:`no result`})})]})}function sC(e){let t=e.input?.plan;return typeof t==`string`&&t.length>0?t:null}function cC(e){let t=e.input;return!!t&&typeof t.old_string==`string`&&typeof t.new_string==`string`}function lC(e){let t=e.input?.edits;return Array.isArray(t)&&t.length>0}function uC(e){return typeof e.input?.content==`string`}function dC(e){return typeof e.input?.command==`string`}function fC(e){return typeof e.input?.url==`string`}function pC(e){return typeof e.input?.query==`string`}function mC(e){switch((e.name??``).toLowerCase()){case`askuserquestion`:return(0,U.jsx)(Vx,{call:e});case`todowrite`:return(0,U.jsx)(Gx,{call:e});case`exitplanmode`:return sC(e)==null?null:(0,U.jsx)(Yx,{call:e});case`edit`:return cC(e)?(0,U.jsx)(RS,{call:e}):null;case`multiedit`:return lC(e)?(0,U.jsx)(RS,{call:e}):null;case`write`:return uC(e)?(0,U.jsx)(RS,{call:e}):null;case`bash`:return dC(e)?(0,U.jsx)(YS,{call:e}):null;case`webfetch`:return fC(e)?(0,U.jsx)(rC,{call:e}):null;case`websearch`:return pC(e)?(0,U.jsx)(oC,{call:e}):null;default:return null}}function hC({call:e}){return(0,U.jsx)(Ux,{todos:e.task_snapshot??[],label:`Tasks`})}function gC({name:e}){let t=Vb(e);return t?(0,U.jsxs)(U.Fragment,{children:[(0,U.jsx)(`span`,{className:`conv-chip-name`,title:e??void 0,children:t.action}),(0,U.jsx)(`span`,{className:`conv-chip-server`,children:t.serverLabel})]}):(0,U.jsx)(`span`,{className:`conv-chip-name`,children:e??`tool`})}var _C=new Set([`TaskCreate`,`TaskUpdate`,`TaskList`]);function vC(e){let t=e[0];return t!=null&&t.name!=null&&_C.has(t.name)&&Array.isArray(t.task_snapshot)}function yC({blocks:e,anchorUuid:t}){let n=eS()===`chat`,r=[],i=0,a=[],o=()=>{a.length&&(r.push((0,U.jsx)(kx,{children:a.join(`
|
|
62
62
|
|
|
63
63
|
`)},`t${r.length}`)),a=[])};for(;i<e.length;){let s=e[i];if(s.kind===`text`){a.push(s.text),i++;continue}if(o(),s.kind===`tool_call`){let t=[];for(;i<e.length&&e[i].kind===`tool_call`;)t.push(e[i]),i++;n||r.push((0,U.jsx)(bC,{calls:t},`r${r.length}`));continue}if(n&&(s.kind===`tool_use`||s.kind===`tool_result`)){i++;continue}r.push((0,U.jsx)(wC,{block:s,anchorUuid:t},`c${r.length}`)),i++}return o(),r.length===0?null:(0,U.jsx)(`div`,{className:`conv-blocks`,children:r})}function bC({calls:e}){return vC(e)?(0,U.jsx)(`div`,{className:`conv-toolrun`,children:(0,U.jsx)(hC,{call:e[0]})}):(0,U.jsxs)(`div`,{className:`conv-toolrun`,children:[e.length>=2&&(0,U.jsxs)(`div`,{className:`conv-toolrun-head`,children:[`tool run · `,e.length,` actions`]}),e.map((e,t)=>(0,U.jsx)(SC,{call:e},t))]})}function xC({result:e,name:t,preview:n}){let r=t===`Read`&&!e.is_error?Px(`Read`,n):``;return r?(0,U.jsx)(jx,{code:e.text,lang:r}):(0,U.jsx)(`pre`,{className:`conv-code conv-code--result`,children:e.text})}function SC({call:e}){if(e.skill_body!=null)return(0,U.jsxs)(`details`,{className:`conv-chip conv-chip--tool conv-chip--skill`,children:[(0,U.jsxs)(`summary`,{children:[(0,U.jsx)(`span`,{className:`conv-chev`,"aria-hidden":`true`}),vx(e.name),` `,(0,U.jsx)(gC,{name:e.name}),(0,U.jsx)(`span`,{className:`conv-chip-preview`,children:e.preview})]}),(0,U.jsxs)(`div`,{className:`conv-chip-body`,children:[(0,U.jsx)(yx,{text:e.skill_body}),(0,U.jsx)(kx,{children:e.skill_body})]})]});let t=mC(e);if(t)return t;let n=e.result?.is_error?` · error`:e.result?.truncated?` · truncated`:``;return(0,U.jsxs)(`details`,{className:`conv-chip conv-chip--tool`,children:[(0,U.jsxs)(`summary`,{children:[(0,U.jsx)(`span`,{className:`conv-chev`,"aria-hidden":`true`}),vx(e.name),` `,(0,U.jsx)(gC,{name:e.name}),(0,U.jsx)(`span`,{className:`conv-chip-preview`,children:e.preview}),n&&(0,U.jsx)(`span`,{className:`conv-chip-status`,children:n})]}),(0,U.jsxs)(`div`,{className:`conv-chip-body conv-chip-body--io`,children:[(0,U.jsxs)(`div`,{className:`conv-tool-io`,children:[(0,U.jsx)(`div`,{className:`conv-tool-io-label`,children:`request`}),(0,U.jsx)(yx,{text:e.input_summary}),(0,U.jsx)(`pre`,{className:`conv-code conv-code--hl`,children:Sx(e.input_summary,`json`)})]}),e.result?(0,U.jsxs)(`div`,{className:`conv-tool-io`,children:[(0,U.jsxs)(`div`,{className:`conv-tool-io-label`,children:[`result`,e.result.is_error?` · error`:` · ok`,e.result.truncated?` · truncated`:``]}),(0,U.jsx)(yx,{text:e.result.text}),(0,U.jsx)(xC,{result:e.result,name:e.name,preview:e.preview}),e.result.media?.map(t=>(0,U.jsx)(ZS,{media:t,toolUseId:e.tool_use_id,context:e.name??`tool`},t.index))]}):(0,U.jsx)(`div`,{className:`conv-tool-io`,children:(0,U.jsx)(`div`,{className:`conv-tool-io-label conv-tool-io-label--none`,children:`no result`})})]})]})}function CC(e){let t=e.split(`
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
6
6
|
<title>cctally dashboard</title>
|
|
7
|
-
<script type="module" crossorigin src="/static/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/static/assets/index-L_FSvwQH.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/static/assets/index-Drpkfv6k.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cctally",
|
|
3
|
-
"version": "1.44.
|
|
3
|
+
"version": "1.44.3",
|
|
4
4
|
"description": "Claude Code usage tracker and local dashboard for Pro/Max subscription limits - weekly cost-per-percent trend, quota forecasts, threshold alerts. ccusage-compatible.",
|
|
5
5
|
"homepage": "https://github.com/omrikais/cctally",
|
|
6
6
|
"repository": {
|