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