cc-transcript 9.1.0__tar.gz → 10.0.0__tar.gz
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.
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/Cargo.lock +130 -12
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/PKG-INFO +1 -1
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/_parser_rs.pyi +14 -0
- cc_transcript-10.0.0/cc_transcript/activity_probe.py +224 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/pyproject.toml +1 -1
- cc_transcript-10.0.0/rust/Cargo.toml +40 -0
- cc_transcript-10.0.0/rust/src/activity.rs +463 -0
- cc_transcript-10.0.0/rust/src/event.rs +288 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/rust/src/filter.rs +41 -44
- cc_transcript-10.0.0/rust/src/lib.rs +22 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/rust/src/mining.rs +132 -252
- cc_transcript-10.0.0/rust/src/parse.rs +628 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/rust/src/protocol.rs +9 -9
- cc_transcript-9.1.0/rust/src/lib.rs → cc_transcript-10.0.0/rust/src/python.rs +57 -45
- cc_transcript-10.0.0/rust/src/types.rs +331 -0
- cc_transcript-10.0.0/rust/src/value.rs +19 -0
- cc_transcript-9.1.0/rust/Cargo.toml +0 -29
- cc_transcript-9.1.0/rust/src/event.rs +0 -459
- cc_transcript-9.1.0/rust/src/value.rs +0 -44
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/Cargo.toml +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/LICENSE +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/README.md +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/__init__.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/__main__.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/activity.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/backend.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/builders.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/cli.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/command.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/context.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/corrections.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/corrections_cli.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/cost.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/decisions.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/discovery.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/disktruth.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/evidence.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/extract/__init__.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/extract/correct.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/facts.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/filterspec.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/ids.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/judge/__init__.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/judge/llm.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/judge/similar.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/judge/verdicts.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/mining/__init__.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/mining/candidates.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/mining/confidence.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/mining/engine.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/mining/filterspec.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/mining/formats.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/mining/signals.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/mining/sourcekind.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/mining/spec.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/mining/store.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/models.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/notifications.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/parser.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/py.typed +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/query.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/render.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/rust.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/sentiment/__init__.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/sentiment/buckets.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/sentiment/engine.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/sentiment/lexicon.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/sentiment/scorespec.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/store.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/cc_transcript/tools.py +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/rust/data/afinn-en-165.tsv +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/rust/data/domain_overrides.tsv +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/rust/src/command.rs +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/rust/src/lexicon.rs +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/rust/src/model.rs +0 -0
- {cc_transcript-9.1.0 → cc_transcript-10.0.0}/rust/src/score.rs +0 -0
|
@@ -42,6 +42,12 @@ version = "0.22.1"
|
|
|
42
42
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
43
43
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
|
44
44
|
|
|
45
|
+
[[package]]
|
|
46
|
+
name = "bitflags"
|
|
47
|
+
version = "2.13.0"
|
|
48
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
49
|
+
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
|
50
|
+
|
|
45
51
|
[[package]]
|
|
46
52
|
name = "bumpalo"
|
|
47
53
|
version = "3.20.3"
|
|
@@ -77,13 +83,21 @@ dependencies = [
|
|
|
77
83
|
"pyo3",
|
|
78
84
|
"rayon",
|
|
79
85
|
"regex",
|
|
80
|
-
"serde",
|
|
81
86
|
"sonic-rs",
|
|
82
87
|
"tree-sitter",
|
|
83
88
|
"tree-sitter-bash",
|
|
84
89
|
"udpipe-rs",
|
|
85
90
|
]
|
|
86
91
|
|
|
92
|
+
[[package]]
|
|
93
|
+
name = "cc_transcript_swift"
|
|
94
|
+
version = "0.8.0"
|
|
95
|
+
dependencies = [
|
|
96
|
+
"cc_transcript_parser",
|
|
97
|
+
"swift-bridge",
|
|
98
|
+
"swift-bridge-build",
|
|
99
|
+
]
|
|
100
|
+
|
|
87
101
|
[[package]]
|
|
88
102
|
name = "cfg-if"
|
|
89
103
|
version = "1.0.4"
|
|
@@ -175,6 +189,22 @@ version = "1.0.2"
|
|
|
175
189
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
176
190
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
177
191
|
|
|
192
|
+
[[package]]
|
|
193
|
+
name = "errno"
|
|
194
|
+
version = "0.3.14"
|
|
195
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
196
|
+
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
|
197
|
+
dependencies = [
|
|
198
|
+
"libc",
|
|
199
|
+
"windows-sys 0.52.0",
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
[[package]]
|
|
203
|
+
name = "fastrand"
|
|
204
|
+
version = "2.4.1"
|
|
205
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
206
|
+
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
|
|
207
|
+
|
|
178
208
|
[[package]]
|
|
179
209
|
name = "faststr"
|
|
180
210
|
version = "0.2.34"
|
|
@@ -327,6 +357,12 @@ dependencies = [
|
|
|
327
357
|
"libc",
|
|
328
358
|
]
|
|
329
359
|
|
|
360
|
+
[[package]]
|
|
361
|
+
name = "linux-raw-sys"
|
|
362
|
+
version = "0.12.1"
|
|
363
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
364
|
+
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
|
365
|
+
|
|
330
366
|
[[package]]
|
|
331
367
|
name = "log"
|
|
332
368
|
version = "0.4.32"
|
|
@@ -366,7 +402,7 @@ checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931"
|
|
|
366
402
|
dependencies = [
|
|
367
403
|
"proc-macro2",
|
|
368
404
|
"quote",
|
|
369
|
-
"syn",
|
|
405
|
+
"syn 2.0.117",
|
|
370
406
|
]
|
|
371
407
|
|
|
372
408
|
[[package]]
|
|
@@ -444,7 +480,7 @@ checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1"
|
|
|
444
480
|
dependencies = [
|
|
445
481
|
"proc-macro2",
|
|
446
482
|
"quote",
|
|
447
|
-
"syn",
|
|
483
|
+
"syn 2.0.117",
|
|
448
484
|
]
|
|
449
485
|
|
|
450
486
|
[[package]]
|
|
@@ -490,7 +526,7 @@ dependencies = [
|
|
|
490
526
|
"proc-macro2",
|
|
491
527
|
"pyo3-macros-backend",
|
|
492
528
|
"quote",
|
|
493
|
-
"syn",
|
|
529
|
+
"syn 2.0.117",
|
|
494
530
|
]
|
|
495
531
|
|
|
496
532
|
[[package]]
|
|
@@ -503,7 +539,7 @@ dependencies = [
|
|
|
503
539
|
"proc-macro2",
|
|
504
540
|
"pyo3-build-config",
|
|
505
541
|
"quote",
|
|
506
|
-
"syn",
|
|
542
|
+
"syn 2.0.117",
|
|
507
543
|
]
|
|
508
544
|
|
|
509
545
|
[[package]]
|
|
@@ -578,7 +614,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
|
|
|
578
614
|
dependencies = [
|
|
579
615
|
"proc-macro2",
|
|
580
616
|
"quote",
|
|
581
|
-
"syn",
|
|
617
|
+
"syn 2.0.117",
|
|
582
618
|
]
|
|
583
619
|
|
|
584
620
|
[[package]]
|
|
@@ -656,7 +692,20 @@ checksum = "5d2ed0b54125315fb36bd021e82d314d1c126548f871634b483f46b31d13cac6"
|
|
|
656
692
|
dependencies = [
|
|
657
693
|
"proc-macro2",
|
|
658
694
|
"quote",
|
|
659
|
-
"syn",
|
|
695
|
+
"syn 2.0.117",
|
|
696
|
+
]
|
|
697
|
+
|
|
698
|
+
[[package]]
|
|
699
|
+
name = "rustix"
|
|
700
|
+
version = "1.1.4"
|
|
701
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
702
|
+
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
|
703
|
+
dependencies = [
|
|
704
|
+
"bitflags",
|
|
705
|
+
"errno",
|
|
706
|
+
"libc",
|
|
707
|
+
"linux-raw-sys",
|
|
708
|
+
"windows-sys 0.52.0",
|
|
660
709
|
]
|
|
661
710
|
|
|
662
711
|
[[package]]
|
|
@@ -727,7 +776,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
|
727
776
|
dependencies = [
|
|
728
777
|
"proc-macro2",
|
|
729
778
|
"quote",
|
|
730
|
-
"syn",
|
|
779
|
+
"syn 2.0.117",
|
|
731
780
|
]
|
|
732
781
|
|
|
733
782
|
[[package]]
|
|
@@ -819,6 +868,62 @@ version = "2.6.1"
|
|
|
819
868
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
820
869
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|
821
870
|
|
|
871
|
+
[[package]]
|
|
872
|
+
name = "swift-bridge"
|
|
873
|
+
version = "0.1.59"
|
|
874
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
875
|
+
checksum = "384ed39ea10f1cefabb197b7d8e67f0034b15a94ccbb1038b8e020da59bfb0be"
|
|
876
|
+
dependencies = [
|
|
877
|
+
"swift-bridge-build",
|
|
878
|
+
"swift-bridge-macro",
|
|
879
|
+
]
|
|
880
|
+
|
|
881
|
+
[[package]]
|
|
882
|
+
name = "swift-bridge-build"
|
|
883
|
+
version = "0.1.59"
|
|
884
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
885
|
+
checksum = "71b36df21e7f8a8b5eeb718d2e71f9cfc308477bfb705981cca705de9767dcb7"
|
|
886
|
+
dependencies = [
|
|
887
|
+
"proc-macro2",
|
|
888
|
+
"swift-bridge-ir",
|
|
889
|
+
"syn 1.0.109",
|
|
890
|
+
"tempfile",
|
|
891
|
+
]
|
|
892
|
+
|
|
893
|
+
[[package]]
|
|
894
|
+
name = "swift-bridge-ir"
|
|
895
|
+
version = "0.1.59"
|
|
896
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
897
|
+
checksum = "c73bd16155df50708b92306945656e57d62d321290a7db490f299f709fb31c83"
|
|
898
|
+
dependencies = [
|
|
899
|
+
"proc-macro2",
|
|
900
|
+
"quote",
|
|
901
|
+
"syn 1.0.109",
|
|
902
|
+
]
|
|
903
|
+
|
|
904
|
+
[[package]]
|
|
905
|
+
name = "swift-bridge-macro"
|
|
906
|
+
version = "0.1.59"
|
|
907
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
908
|
+
checksum = "a1a13dc0dc875d85341dec5b5344a7d713f20eb5650b71086b27d09a6ece272f"
|
|
909
|
+
dependencies = [
|
|
910
|
+
"proc-macro2",
|
|
911
|
+
"quote",
|
|
912
|
+
"swift-bridge-ir",
|
|
913
|
+
"syn 1.0.109",
|
|
914
|
+
]
|
|
915
|
+
|
|
916
|
+
[[package]]
|
|
917
|
+
name = "syn"
|
|
918
|
+
version = "1.0.109"
|
|
919
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
920
|
+
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
|
921
|
+
dependencies = [
|
|
922
|
+
"proc-macro2",
|
|
923
|
+
"quote",
|
|
924
|
+
"unicode-ident",
|
|
925
|
+
]
|
|
926
|
+
|
|
822
927
|
[[package]]
|
|
823
928
|
name = "syn"
|
|
824
929
|
version = "2.0.117"
|
|
@@ -836,6 +941,19 @@ version = "0.13.5"
|
|
|
836
941
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
837
942
|
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
838
943
|
|
|
944
|
+
[[package]]
|
|
945
|
+
name = "tempfile"
|
|
946
|
+
version = "3.27.0"
|
|
947
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
948
|
+
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
|
949
|
+
dependencies = [
|
|
950
|
+
"fastrand",
|
|
951
|
+
"getrandom 0.3.4",
|
|
952
|
+
"once_cell",
|
|
953
|
+
"rustix",
|
|
954
|
+
"windows-sys 0.52.0",
|
|
955
|
+
]
|
|
956
|
+
|
|
839
957
|
[[package]]
|
|
840
958
|
name = "thiserror"
|
|
841
959
|
version = "1.0.69"
|
|
@@ -862,7 +980,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
|
|
862
980
|
dependencies = [
|
|
863
981
|
"proc-macro2",
|
|
864
982
|
"quote",
|
|
865
|
-
"syn",
|
|
983
|
+
"syn 2.0.117",
|
|
866
984
|
]
|
|
867
985
|
|
|
868
986
|
[[package]]
|
|
@@ -873,7 +991,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
|
|
873
991
|
dependencies = [
|
|
874
992
|
"proc-macro2",
|
|
875
993
|
"quote",
|
|
876
|
-
"syn",
|
|
994
|
+
"syn 2.0.117",
|
|
877
995
|
]
|
|
878
996
|
|
|
879
997
|
[[package]]
|
|
@@ -1041,7 +1159,7 @@ dependencies = [
|
|
|
1041
1159
|
"bumpalo",
|
|
1042
1160
|
"proc-macro2",
|
|
1043
1161
|
"quote",
|
|
1044
|
-
"syn",
|
|
1162
|
+
"syn 2.0.117",
|
|
1045
1163
|
"wasm-bindgen-shared",
|
|
1046
1164
|
]
|
|
1047
1165
|
|
|
@@ -1225,7 +1343,7 @@ checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
|
|
|
1225
1343
|
dependencies = [
|
|
1226
1344
|
"proc-macro2",
|
|
1227
1345
|
"quote",
|
|
1228
|
-
"syn",
|
|
1346
|
+
"syn 2.0.117",
|
|
1229
1347
|
]
|
|
1230
1348
|
|
|
1231
1349
|
[[package]]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
3
5
|
from cc_transcript.models import PrintResult, TranscriptEvent
|
|
4
6
|
|
|
5
7
|
class ParseStream:
|
|
@@ -41,3 +43,15 @@ def score_post_process(spec_json: str, buckets: list[list[str]], raw: list[int],
|
|
|
41
43
|
|
|
42
44
|
def command_prefixes(commands: list[str], /) -> list[list[str]]:
|
|
43
45
|
"""Permission-style prefixes per command line, parsed in parallel off the GIL."""
|
|
46
|
+
|
|
47
|
+
def mine_signals(raw: bytes, spec_json: str, /) -> list[dict[str, Any]]:
|
|
48
|
+
"""Parses raw transcript bytes and mines signal dicts per the portable mining spec."""
|
|
49
|
+
|
|
50
|
+
def session_activity_probe(
|
|
51
|
+
path: str, waiting_tools: list[str] | None = ..., human_facing_tools: list[str] | None = ..., /
|
|
52
|
+
) -> dict[str, Any]:
|
|
53
|
+
"""Parses the transcript at ``path`` and returns the session-activity verdict dict.
|
|
54
|
+
|
|
55
|
+
The dict carries ``is_waiting: bool``, ``mid_tool: bool``, ``last_event_epoch: int | None``,
|
|
56
|
+
and ``pending: list[dict]`` with ``tool_use_id``/``name``/``kind`` per contributing call.
|
|
57
|
+
"""
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Dual-backend session-activity probe — captain-hook's ``is_waiting`` oracle over one transcript.
|
|
2
|
+
|
|
3
|
+
:func:`rust_probe_backend` resolves the Rust extension when built, else None to fall back
|
|
4
|
+
to the Python reference. :func:`session_activity_probe` is the public dual-backend entry:
|
|
5
|
+
it reads a ``.jsonl`` transcript, runs the oracle over its parsed events, and returns a
|
|
6
|
+
:class:`SessionActivityProbe`; :func:`probe_events` is the Python reference the Rust probe
|
|
7
|
+
stays identical to. The verdict spans ephemeral waits (waiting tools, backgrounded
|
|
8
|
+
Agent/Task/Bash, subagentless Agent/Task) over the current turn plus pending async
|
|
9
|
+
launches (async Agent/Task, live Workflow) session-wide, with completion read from
|
|
10
|
+
queue-operation ``<tool-use-id>`` markers, and flags unanswered non-human-facing tool
|
|
11
|
+
calls in the current turn as mid-tool.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import TYPE_CHECKING, Literal
|
|
19
|
+
|
|
20
|
+
from cc_transcript.activity import native_user_classifier
|
|
21
|
+
from cc_transcript.models import (
|
|
22
|
+
AssistantEvent,
|
|
23
|
+
OtherEvent,
|
|
24
|
+
SystemEvent,
|
|
25
|
+
ToolResultBlock,
|
|
26
|
+
ToolUseBlock,
|
|
27
|
+
UserEvent,
|
|
28
|
+
)
|
|
29
|
+
from cc_transcript.parser import parse_events_from_bytes
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from collections.abc import Sequence
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from types import ModuleType
|
|
35
|
+
|
|
36
|
+
from cc_transcript.models import TranscriptEvent
|
|
37
|
+
|
|
38
|
+
DEFAULT_WAITING_TOOLS = frozenset({"Monitor", "ScheduleWakeup", "SendMessage", "TeamCreate"})
|
|
39
|
+
DEFAULT_HUMAN_FACING_TOOLS = frozenset({"AskUserQuestion", "ExitPlanMode"})
|
|
40
|
+
|
|
41
|
+
PendingKind = Literal[
|
|
42
|
+
"waiting_tool",
|
|
43
|
+
"background",
|
|
44
|
+
"subagentless_task",
|
|
45
|
+
"pending_async_task",
|
|
46
|
+
"pending_async_workflow",
|
|
47
|
+
"mid_tool",
|
|
48
|
+
]
|
|
49
|
+
"""How a tool call contributes to the session-activity verdict."""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True, slots=True)
|
|
53
|
+
class PendingItem:
|
|
54
|
+
"""One tool call contributing to the session-activity verdict.
|
|
55
|
+
|
|
56
|
+
Attributes:
|
|
57
|
+
tool_use_id: The contributing call's tool-use id.
|
|
58
|
+
name: The tool's name exactly as invoked.
|
|
59
|
+
kind: How the call contributes to the verdict.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
tool_use_id: str | None
|
|
63
|
+
name: str
|
|
64
|
+
kind: PendingKind
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True, slots=True)
|
|
68
|
+
class SessionActivityProbe:
|
|
69
|
+
"""The session-activity verdict over one transcript.
|
|
70
|
+
|
|
71
|
+
Attributes:
|
|
72
|
+
is_waiting: Whether the session waits on ephemeral or pending async work.
|
|
73
|
+
mid_tool: Whether the current turn has an unanswered non-human-facing call.
|
|
74
|
+
pending: The contributing calls, in document order, deduped by tool-use id.
|
|
75
|
+
last_event_epoch: The latest envelope timestamp as epoch seconds, or None.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
is_waiting: bool
|
|
79
|
+
mid_tool: bool
|
|
80
|
+
pending: tuple[PendingItem, ...]
|
|
81
|
+
last_event_epoch: int | None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def completed(completions: Sequence[str], tool_use_id: str) -> bool:
|
|
85
|
+
marker = f"<tool-use-id>{tool_use_id}</tool-use-id>"
|
|
86
|
+
return any(marker in content for content in completions)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def ephemeral_wait(block: ToolUseBlock, waiting_tools: frozenset[str]) -> PendingKind | None:
|
|
90
|
+
if block.name in waiting_tools:
|
|
91
|
+
return "waiting_tool"
|
|
92
|
+
match block.name:
|
|
93
|
+
case "Agent" | "Task" | "Bash" if block.input.get("run_in_background") is True:
|
|
94
|
+
return "background"
|
|
95
|
+
case "Agent" | "Task" if not isinstance(block.input.get("subagent_type"), str):
|
|
96
|
+
return "subagentless_task"
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def pending_async(
|
|
101
|
+
block: ToolUseBlock, result: ToolResultBlock | None, completions: Sequence[str]
|
|
102
|
+
) -> PendingKind | None:
|
|
103
|
+
match block.name:
|
|
104
|
+
case "Agent" | "Task" if result is not None and result.is_async and not completed(completions, block.id):
|
|
105
|
+
return "pending_async_task"
|
|
106
|
+
case "Workflow" if not completed(completions, block.id):
|
|
107
|
+
return "pending_async_workflow"
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def probe_events(
|
|
112
|
+
events: Sequence[TranscriptEvent],
|
|
113
|
+
*,
|
|
114
|
+
waiting_tools: frozenset[str] = DEFAULT_WAITING_TOOLS,
|
|
115
|
+
human_facing_tools: frozenset[str] = DEFAULT_HUMAN_FACING_TOOLS,
|
|
116
|
+
) -> SessionActivityProbe:
|
|
117
|
+
"""The Python reference oracle over parsed events, twin of the Rust probe."""
|
|
118
|
+
results = {
|
|
119
|
+
block.tool_use_id: block
|
|
120
|
+
for event in events
|
|
121
|
+
if isinstance(event, UserEvent | AssistantEvent)
|
|
122
|
+
for block in event.blocks
|
|
123
|
+
if isinstance(block, ToolResultBlock)
|
|
124
|
+
}
|
|
125
|
+
completions = [
|
|
126
|
+
content
|
|
127
|
+
for event in events
|
|
128
|
+
if isinstance(event, OtherEvent) and event.type == "queue-operation"
|
|
129
|
+
if isinstance(content := event.raw.get("content"), str)
|
|
130
|
+
]
|
|
131
|
+
turn_start = next(
|
|
132
|
+
(
|
|
133
|
+
index
|
|
134
|
+
for index in reversed(range(len(events)))
|
|
135
|
+
if isinstance(event := events[index], UserEvent) and native_user_classifier(event)
|
|
136
|
+
),
|
|
137
|
+
0,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
is_waiting = False
|
|
141
|
+
mid_tool = False
|
|
142
|
+
pending: list[PendingItem] = []
|
|
143
|
+
seen: set[str] = set()
|
|
144
|
+
for index, event in enumerate(events):
|
|
145
|
+
if not isinstance(event, UserEvent | AssistantEvent):
|
|
146
|
+
continue
|
|
147
|
+
for block in event.blocks:
|
|
148
|
+
if not isinstance(block, ToolUseBlock):
|
|
149
|
+
continue
|
|
150
|
+
result = results.get(block.id)
|
|
151
|
+
if result is not None and result.is_error:
|
|
152
|
+
continue
|
|
153
|
+
in_current_turn = index >= turn_start
|
|
154
|
+
waiting_kind = (ephemeral_wait(block, waiting_tools) if in_current_turn else None) or pending_async(
|
|
155
|
+
block, result, completions
|
|
156
|
+
)
|
|
157
|
+
unmatched = in_current_turn and result is None and block.name not in human_facing_tools
|
|
158
|
+
is_waiting = is_waiting or waiting_kind is not None
|
|
159
|
+
mid_tool = mid_tool or unmatched
|
|
160
|
+
kind = waiting_kind or ("mid_tool" if unmatched else None)
|
|
161
|
+
if kind is None or block.id in seen:
|
|
162
|
+
continue
|
|
163
|
+
seen.add(block.id)
|
|
164
|
+
pending.append(PendingItem(tool_use_id=block.id, name=block.name, kind=kind))
|
|
165
|
+
return SessionActivityProbe(
|
|
166
|
+
is_waiting=is_waiting,
|
|
167
|
+
mid_tool=mid_tool,
|
|
168
|
+
pending=tuple(pending),
|
|
169
|
+
last_event_epoch=max(
|
|
170
|
+
(
|
|
171
|
+
int(event.meta.timestamp.timestamp())
|
|
172
|
+
for event in events
|
|
173
|
+
if isinstance(event, UserEvent | AssistantEvent | SystemEvent)
|
|
174
|
+
),
|
|
175
|
+
default=None,
|
|
176
|
+
),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def rust_probe_backend() -> ModuleType | None:
|
|
181
|
+
"""The Rust probe when the extension is built with it; else None → the Python reference."""
|
|
182
|
+
if os.environ.get("CC_TRANSCRIPT_DISABLE_RUST"):
|
|
183
|
+
return None
|
|
184
|
+
try:
|
|
185
|
+
from cc_transcript import _parser_rs
|
|
186
|
+
except ImportError:
|
|
187
|
+
return None
|
|
188
|
+
return _parser_rs if hasattr(_parser_rs, "session_activity_probe") else None
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def session_activity_probe(
|
|
192
|
+
path: Path,
|
|
193
|
+
*,
|
|
194
|
+
waiting_tools: frozenset[str] = DEFAULT_WAITING_TOOLS,
|
|
195
|
+
human_facing_tools: frozenset[str] = DEFAULT_HUMAN_FACING_TOOLS,
|
|
196
|
+
) -> SessionActivityProbe:
|
|
197
|
+
"""Probes the transcript at ``path`` for session activity via the active backend.
|
|
198
|
+
|
|
199
|
+
The Rust backend reads and parses the file inside the extension in one pass; the
|
|
200
|
+
Python reference parses via :func:`~cc_transcript.parser.parse_events_from_bytes`
|
|
201
|
+
and runs :func:`probe_events`. Both yield identical verdicts.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
path: The ``.jsonl`` transcript to probe.
|
|
205
|
+
waiting_tools: Tool names whose calls in the current turn mark the session waiting.
|
|
206
|
+
human_facing_tools: Tool names whose unanswered calls are the user's move, never mid-tool.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
The session-activity verdict.
|
|
210
|
+
"""
|
|
211
|
+
rust = rust_probe_backend()
|
|
212
|
+
if rust is None:
|
|
213
|
+
return probe_events(
|
|
214
|
+
parse_events_from_bytes(path.read_bytes()),
|
|
215
|
+
waiting_tools=waiting_tools,
|
|
216
|
+
human_facing_tools=human_facing_tools,
|
|
217
|
+
)
|
|
218
|
+
payload = rust.session_activity_probe(str(path), sorted(waiting_tools), sorted(human_facing_tools))
|
|
219
|
+
return SessionActivityProbe(
|
|
220
|
+
is_waiting=payload["is_waiting"],
|
|
221
|
+
mid_tool=payload["mid_tool"],
|
|
222
|
+
pending=tuple(PendingItem(**item) for item in payload["pending"]),
|
|
223
|
+
last_event_epoch=payload["last_event_epoch"],
|
|
224
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "cc_transcript_parser"
|
|
3
|
+
version = "0.8.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
|
|
6
|
+
[lib]
|
|
7
|
+
name = "_parser_rs"
|
|
8
|
+
crate-type = ["cdylib", "rlib"]
|
|
9
|
+
|
|
10
|
+
[features]
|
|
11
|
+
default = ["python"]
|
|
12
|
+
python = [
|
|
13
|
+
"dep:pyo3",
|
|
14
|
+
"dep:rayon",
|
|
15
|
+
"dep:crossbeam-channel",
|
|
16
|
+
"dep:num_cpus",
|
|
17
|
+
"dep:udpipe-rs",
|
|
18
|
+
"dep:dirs",
|
|
19
|
+
"dep:tree-sitter",
|
|
20
|
+
"dep:tree-sitter-bash",
|
|
21
|
+
]
|
|
22
|
+
extension-module = ["python", "pyo3/extension-module"]
|
|
23
|
+
swift = []
|
|
24
|
+
|
|
25
|
+
[dependencies]
|
|
26
|
+
pyo3 = { version = "0.28", features = ["abi3-py313", "chrono"], optional = true }
|
|
27
|
+
sonic-rs = "0.5"
|
|
28
|
+
chrono = { version = "0.4", default-features = false, features = ["std"] }
|
|
29
|
+
rayon = { version = "1.10", optional = true }
|
|
30
|
+
crossbeam-channel = { version = "0.5", optional = true }
|
|
31
|
+
num_cpus = { version = "1", optional = true }
|
|
32
|
+
memchr = "2.7"
|
|
33
|
+
once_cell = "1"
|
|
34
|
+
regex = "1"
|
|
35
|
+
udpipe-rs = { version = "0.2", optional = true }
|
|
36
|
+
dirs = { version = "5", optional = true }
|
|
37
|
+
# Grammar-pinned to the Python wheel: tree_sitter_bash 0.25.1 both sides (same
|
|
38
|
+
# bash grammar), tree-sitter core 0.26 matches the wheel's tree-sitter 0.26.0.
|
|
39
|
+
tree-sitter = { version = "0.26", optional = true }
|
|
40
|
+
tree-sitter-bash = { version = "0.25", optional = true }
|