cc-transcript 14.16.1__tar.gz → 14.22.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.

Potentially problematic release.


This version of cc-transcript might be problematic. Click here for more details.

Files changed (162) hide show
  1. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/PKG-INFO +1 -1
  2. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/__init__.py +2 -0
  3. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/__init__.pyi +4 -0
  4. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/_native.pyi +9 -1
  5. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/activity.py +176 -60
  6. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/query.py +518 -78
  7. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/render.py +25 -6
  8. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/pyproject.toml +1 -1
  9. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/benches/parse.rs +10 -1
  10. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/activity.rs +90 -7
  11. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/context.rs +14 -5
  12. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/parse.rs +1 -0
  13. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/render.rs +135 -18
  14. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/types.rs +1 -0
  15. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/python.rs +68 -22
  16. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/render.rs +2 -0
  17. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/views/attachment.rs +9 -1
  18. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/Cargo.lock +0 -0
  19. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/Cargo.toml +0 -0
  20. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/LICENSE +0 -0
  21. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/README.md +0 -0
  22. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/activity_probe.py +0 -0
  23. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/builders.py +0 -0
  24. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/codex.py +0 -0
  25. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/command.py +0 -0
  26. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/context.py +0 -0
  27. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/corrections.py +0 -0
  28. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/cost.py +0 -0
  29. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/decision_store.py +0 -0
  30. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/decisions.py +0 -0
  31. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/discovery.py +0 -0
  32. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/disktruth.py +0 -0
  33. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/evidence.py +0 -0
  34. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/extract/__init__.py +0 -0
  35. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/extract/correct.py +0 -0
  36. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/facts.py +0 -0
  37. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/filterspec.py +0 -0
  38. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/heartbeats.py +0 -0
  39. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/ids.py +0 -0
  40. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/judge/__init__.py +0 -0
  41. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/judge/llm.py +0 -0
  42. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/judge/similar.py +0 -0
  43. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/judge/verdicts.py +0 -0
  44. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/ledger.py +0 -0
  45. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/literals.py +0 -0
  46. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/mining/__init__.py +0 -0
  47. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/mining/candidates.py +0 -0
  48. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/mining/confidence.py +0 -0
  49. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/mining/engine.py +0 -0
  50. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/mining/formats.py +0 -0
  51. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/mining/sampling.py +0 -0
  52. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/mining/signals.py +0 -0
  53. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/mining/sourcekind.py +0 -0
  54. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/mining/spec.py +0 -0
  55. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/mining/store.py +0 -0
  56. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/models.py +0 -0
  57. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/nlp.py +0 -0
  58. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/notifications.py +0 -0
  59. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/parser.py +0 -0
  60. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/py.typed +0 -0
  61. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/sentiment/__init__.py +0 -0
  62. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/sentiment/buckets.py +0 -0
  63. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/sentiment/data/afinn-en-165.tsv +0 -0
  64. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/sentiment/data/domain_overrides.tsv +0 -0
  65. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/sentiment/data/en-ewt.udpipe +0 -0
  66. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/sentiment/engine.py +0 -0
  67. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/sentiment/lexicon.py +0 -0
  68. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/sentiment/scorespec.py +0 -0
  69. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/synthetic.py +0 -0
  70. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/tools.py +0 -0
  71. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/cc_transcript/watch.py +0 -0
  72. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/Cargo.toml +0 -0
  73. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/commands/blame.rs +0 -0
  74. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/commands/corrections.rs +0 -0
  75. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/commands/digest.rs +0 -0
  76. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/commands/facts.rs +0 -0
  77. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/commands/grep.rs +0 -0
  78. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/commands/list.rs +0 -0
  79. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/commands/mod.rs +0 -0
  80. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/commands/scratchpad.rs +0 -0
  81. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/commands/show.rs +0 -0
  82. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/commands/slice.rs +0 -0
  83. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/commands/stats.rs +0 -0
  84. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/commands/watch.rs +0 -0
  85. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/lib.rs +0 -0
  86. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/main.rs +0 -0
  87. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/output.rs +0 -0
  88. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/target.rs +0 -0
  89. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/cli/src/timearg.rs +0 -0
  90. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/Cargo.toml +0 -0
  91. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/benches/render.rs +0 -0
  92. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/benches/toolcall.rs +0 -0
  93. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/actor.rs +0 -0
  94. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/blame.rs +0 -0
  95. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/buckets.rs +0 -0
  96. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/codex/discovery.rs +0 -0
  97. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/codex/lower.rs +0 -0
  98. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/codex/mod.rs +0 -0
  99. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/codex/parse.rs +0 -0
  100. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/codex/probe.rs +0 -0
  101. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/codex/protocol.rs +0 -0
  102. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/codex/types.rs +0 -0
  103. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/command.rs +0 -0
  104. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/corrections.rs +0 -0
  105. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/cost.rs +0 -0
  106. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/discovery.rs +0 -0
  107. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/facts.rs +0 -0
  108. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/feedback.rs +0 -0
  109. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/filter.rs +0 -0
  110. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/gateway.rs +0 -0
  111. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/ids.rs +0 -0
  112. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/judge.rs +0 -0
  113. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/lib.rs +0 -0
  114. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/literals/command.rs +0 -0
  115. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/literals/corrections.rs +0 -0
  116. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/literals/feedback.rs +0 -0
  117. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/literals/mining.rs +0 -0
  118. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/literals/mod.rs +0 -0
  119. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/literals/protocol.rs +0 -0
  120. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/notifications.rs +0 -0
  121. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/protocol.rs +0 -0
  122. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/pystr.rs +0 -0
  123. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/query.rs +0 -0
  124. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/rng.rs +0 -0
  125. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/schema.rs +0 -0
  126. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/sqlite.rs +0 -0
  127. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/toolcall.rs +0 -0
  128. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/value.rs +0 -0
  129. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/core/src/watch.rs +0 -0
  130. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/Cargo.toml +0 -0
  131. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/build.rs +0 -0
  132. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/data/command_prefix_pins.tsv +0 -0
  133. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/actor_bridge.rs +0 -0
  134. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/bin/stub_gen.rs +0 -0
  135. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/codex.rs +0 -0
  136. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/command.rs +0 -0
  137. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/context.rs +0 -0
  138. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/corrections.rs +0 -0
  139. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/discovery.rs +0 -0
  140. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/feedback.rs +0 -0
  141. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/judge.rs +0 -0
  142. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/lexicon.rs +0 -0
  143. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/lib.rs +0 -0
  144. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/mining.rs +0 -0
  145. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/nlp.rs +0 -0
  146. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/score.rs +0 -0
  147. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/sqlite.rs +0 -0
  148. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/toolcall.rs +0 -0
  149. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/views/blocks.rs +0 -0
  150. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/views/command.rs +0 -0
  151. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/views/convert.rs +0 -0
  152. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/views/dunder.rs +0 -0
  153. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/views/events.rs +0 -0
  154. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/views/meta.rs +0 -0
  155. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/views/mod.rs +0 -0
  156. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/views/print.rs +0 -0
  157. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/views/store.rs +0 -0
  158. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/views/system.rs +0 -0
  159. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/views/toolcall.rs +0 -0
  160. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/views/toolresult.rs +0 -0
  161. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/views/transcript.rs +0 -0
  162. {cc_transcript-14.16.1 → cc_transcript-14.22.0}/rust/crates/py/src/watch.rs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc-transcript
3
- Version: 14.16.1
3
+ Version: 14.22.0
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -197,6 +197,7 @@ EXPORTS: dict[str, str] = {
197
197
  "Lifecycle",
198
198
  ),
199
199
  "cc_transcript.activity": (
200
+ "ActivityLift",
200
201
  "Edit",
201
202
  "SessionActivity",
202
203
  "ToolUse",
@@ -240,6 +241,7 @@ EXPORTS: dict[str, str] = {
240
241
  "DeepSession",
241
242
  "DeepView",
242
243
  "FileRef",
244
+ "PredicateInputs",
243
245
  "Session",
244
246
  "SubagentIndex",
245
247
  "SubagentSession",
@@ -2,6 +2,7 @@
2
2
  # isort: off
3
3
 
4
4
  from cc_transcript.activity import (
5
+ ActivityLift as ActivityLift,
5
6
  Edit as Edit,
6
7
  SessionActivity as SessionActivity,
7
8
  ToolUse as ToolUse,
@@ -207,6 +208,7 @@ from cc_transcript.query import (
207
208
  DeepSession as DeepSession,
208
209
  DeepView as DeepView,
209
210
  FileRef as FileRef,
211
+ PredicateInputs as PredicateInputs,
210
212
  Session as Session,
211
213
  SubagentIndex as SubagentIndex,
212
214
  SubagentSession as SubagentSession,
@@ -304,6 +306,7 @@ EXPORTS: dict[str, str]
304
306
  __all__ = [
305
307
  "ASSISTANTS",
306
308
  "Action",
309
+ "ActivityLift",
307
310
  "ApiError",
308
311
  "ApplyPatchCall",
309
312
  "AskUserQuestionResult",
@@ -390,6 +393,7 @@ __all__ = [
390
393
  "PRICING",
391
394
  "PatchEdit",
392
395
  "Plugin",
396
+ "PredicateInputs",
393
397
  "PreservedMessages",
394
398
  "PreservedSegment",
395
399
  "PrintMessage",
@@ -108,6 +108,7 @@ __all__ = [
108
108
  "activity_hunk_overlap",
109
109
  "activity_lift",
110
110
  "activity_lift_from_events",
111
+ "activity_lift_tail",
111
112
  "activity_overlap_between",
112
113
  "activity_parse_show_hunks",
113
114
  "bucket_events_from_events",
@@ -1497,12 +1498,17 @@ class QueuedCommand:
1497
1498
  plain-string prompt (e.g. an image-paste payload).
1498
1499
  command_mode: How the command was queued, e.g. ``prompt`` or
1499
1500
  ``task-notification``, or None.
1501
+ origin: Who sent a ``prompt``-mode command: ``human`` for the user,
1502
+ ``peer`` for another agent or session, ``channel`` for an MCP
1503
+ channel event; None when the transcript does not record it.
1500
1504
  """
1501
1505
  __match_args__: typing.ClassVar[tuple[str, ...]]
1502
1506
  @property
1503
1507
  def prompt(self) -> typing.Optional[builtins.str]: ...
1504
1508
  @property
1505
1509
  def command_mode(self) -> typing.Optional[builtins.str]: ...
1510
+ @property
1511
+ def origin(self) -> typing.Optional[builtins.str]: ...
1506
1512
 
1507
1513
  @typing.final
1508
1514
  class ReadCall(ToolCallBase):
@@ -2295,6 +2301,8 @@ def activity_lift(path: builtins.str, max_events: builtins.int) -> dict[str, typ
2295
2301
 
2296
2302
  def activity_lift_from_events(events: list[models.TranscriptEvent], opener_flags: typing.Optional[typing.Sequence[builtins.bool]] = None) -> list[dict[str, typing.Any]]: ...
2297
2303
 
2304
+ def activity_lift_tail(events: list[models.TranscriptEvent], opener_flags: typing.Optional[typing.Sequence[builtins.bool]] = None, open_turn: builtins.bool = False) -> dict[str, typing.Any]: ...
2305
+
2298
2306
  def activity_overlap_between(incorrect: typing.Sequence[tuple[builtins.str, builtins.str]], correction: typing.Sequence[tuple[builtins.str, builtins.str]]) -> builtins.float: ...
2299
2307
 
2300
2308
  def activity_parse_show_hunks(diff: builtins.str) -> builtins.list[tuple[builtins.str, builtins.str]]: ...
@@ -2492,7 +2500,7 @@ def render_tool_call(name: builtins.str, input_json: builtins.str, turn_chars: b
2492
2500
 
2493
2501
  def render_tool_call_view(call: tools.ToolCall, turn_chars: builtins.int, tool_chars: builtins.int) -> builtins.str: ...
2494
2502
 
2495
- def render_turn_from_events(prompt: builtins.str, events: list[models.TranscriptEvent], turn_chars: builtins.int, tool_chars: builtins.int) -> builtins.str: ...
2503
+ def render_turn_from_events(prompt: builtins.str, events: list[models.TranscriptEvent], turn_chars: builtins.int, tool_chars: builtins.int, tool_results: builtins.bool) -> builtins.str: ...
2496
2504
 
2497
2505
  def score_post_process(spec_json: builtins.str, buckets: typing.Sequence[typing.Sequence[builtins.str]], raw: typing.Sequence[builtins.int]) -> builtins.list[builtins.int]: ...
2498
2506
 
@@ -13,8 +13,8 @@ products.
13
13
  from __future__ import annotations
14
14
 
15
15
  from collections.abc import Callable
16
- from dataclasses import dataclass
17
- from typing import TYPE_CHECKING
16
+ from dataclasses import dataclass, replace
17
+ from typing import TYPE_CHECKING, Any
18
18
 
19
19
  from cc_transcript import _native
20
20
  from cc_transcript.discovery import TranscriptExpiredError, resolve
@@ -25,7 +25,7 @@ from cc_transcript.parser import parse
25
25
  from cc_transcript.tools import edits_of, parse_tool_call, parse_tool_result
26
26
 
27
27
  if TYPE_CHECKING:
28
- from collections.abc import Sequence
28
+ from collections.abc import Mapping, Sequence
29
29
  from datetime import datetime
30
30
  from pathlib import Path
31
31
 
@@ -181,64 +181,10 @@ class SessionActivity:
181
181
  turn; everything else folds into the current one. Events before the
182
182
  first qualifying prompt form turn 0 with prompt ``""``.
183
183
  """
184
- evs = list(events)
185
- opener_flags = (
186
- None
187
- if user_classifier is native_user_classifier
188
- else [bool(user_classifier(event)) if isinstance(event, UserEvent) else False for event in evs]
184
+ return cls(
185
+ session_id=session_id,
186
+ turns=ActivityLift(session_id, user_classifier=user_classifier).extend(events).turns,
189
187
  )
190
- tool_blocks = {
191
- event_idx: tuple(block for block in event.blocks if isinstance(block, ToolUseBlock))
192
- for event_idx, event in enumerate(evs)
193
- if isinstance(event, AssistantEvent)
194
- }
195
- tool_block_positions: dict[int, int] = {}
196
- turns: list[Turn] = []
197
- for index, skeleton in enumerate(_native.activity_lift_from_events(evs, opener_flags)):
198
- tool_uses: list[ToolUse] = []
199
- for use in skeleton["tool_uses"]:
200
- event_idx = use["event_idx"]
201
- event = evs[event_idx]
202
- block_position = tool_block_positions.get(event_idx, 0)
203
- block = tool_blocks[event_idx][block_position]
204
- tool_block_positions[event_idx] = block_position + 1
205
- result_event_idx = use["result_event_idx"]
206
- if result_event_idx is None:
207
- result = None
208
- result_ts = None
209
- else:
210
- result_event = evs[result_event_idx]
211
- result = next(
212
- candidate
213
- for candidate in reversed(result_event.blocks)
214
- if isinstance(candidate, ToolResultBlock) and candidate.tool_use_id == use["tool_use_id"]
215
- )
216
- result_ts = result_event.meta.timestamp
217
- call = parse_tool_call(block.name, block.input, on_error="other")
218
- tool_uses.append(
219
- ToolUse(
220
- ref=EventRef(session_id, event.meta.uuid, block.id),
221
- call=call,
222
- result=result,
223
- result_ts=result_ts,
224
- edits=edits_of(call),
225
- turn_index=index,
226
- ts=event.meta.timestamp,
227
- )
228
- )
229
- started_idx = skeleton["started_idx"]
230
- ended_idx = skeleton["ended_idx"]
231
- turns.append(
232
- Turn(
233
- index=index,
234
- prompt=skeleton["prompt"],
235
- started_at=None if started_idx is None else evs[started_idx].meta.timestamp,
236
- ended_at=None if ended_idx is None else evs[ended_idx].meta.timestamp,
237
- events=tuple(evs[skeleton["start"] : skeleton["end"]]),
238
- tool_uses=tuple(tool_uses),
239
- )
240
- )
241
- return cls(session_id=session_id, turns=tuple(turns))
242
188
 
243
189
  @classmethod
244
190
  def from_session(
@@ -326,6 +272,176 @@ class SessionActivity:
326
272
  return tuple(same_turn + later)
327
273
 
328
274
 
275
+ class ActivityLift:
276
+ """A session's lift that grows with its transcript.
277
+
278
+ Each :meth:`extend` folds the events appended since the previous call into
279
+ the activity lifted so far: closed turns are kept, the open turn grows by
280
+ the appended events alone, and a tool use whose result arrives in the tail
281
+ is re-paired wherever it sits. The result always equals
282
+ :meth:`SessionActivity.from_events` over every event fed so far. A step
283
+ costs the appended events, the tool uses they re-pair, and copies of the
284
+ turn tuple and of the open turn's event and tool-use tuples; it never
285
+ re-derives the session.
286
+
287
+ Feed only the events appended since the previous call: nothing checks
288
+ provenance, so events fed twice lift twice. The contract is append-only:
289
+ a cursor's transcript is assumed never rewritten behind the events it has
290
+ been fed, and a transcript rewritten in place, rather than appended to,
291
+ needs a new cursor. Claude Code and Codex transcripts satisfy it.
292
+
293
+ The classifier is read once, at construction, and must be deterministic
294
+ and event-only: its answer for an event has to stay the same as later
295
+ events are appended, since turns lifted under it are never revisited.
296
+
297
+ Example:
298
+ >>> lift = ActivityLift(session_id)
299
+ >>> activity = lift.extend(events)
300
+ >>> lift.extend(appended) == SessionActivity.from_events(session_id, [*events, *appended])
301
+ True
302
+ """
303
+
304
+ __slots__ = ("_activity", "_results", "_session_id", "_user_classifier", "_uses")
305
+
306
+ def __init__(self, session_id: SessionId, *, user_classifier: UserClassifier = native_user_classifier) -> None:
307
+ self._session_id = session_id
308
+ self._user_classifier = user_classifier
309
+ self._activity = SessionActivity(session_id=session_id, turns=())
310
+ self._uses: dict[ToolUseId, tuple[tuple[int, int], ...]] = {}
311
+ self._results: dict[ToolUseId, UserEvent] = {}
312
+
313
+ @property
314
+ def session_id(self) -> SessionId:
315
+ """The session being lifted."""
316
+ return self._session_id
317
+
318
+ @property
319
+ def user_classifier(self) -> UserClassifier:
320
+ """Decides which user events open turns."""
321
+ return self._user_classifier
322
+
323
+ @property
324
+ def activity(self) -> SessionActivity:
325
+ """Everything lifted so far."""
326
+ return self._activity
327
+
328
+ def extend(self, events: Sequence[TranscriptEvent]) -> SessionActivity:
329
+ """Lifts ``events``, appended after everything fed so far, into :attr:`activity`."""
330
+ tail = list(events)
331
+ if not tail:
332
+ return self._activity
333
+ opener_flags = (
334
+ None
335
+ if self._user_classifier is native_user_classifier
336
+ else [bool(self._user_classifier(event)) if isinstance(event, UserEvent) else False for event in tail]
337
+ )
338
+ turns = list(self._activity.turns)
339
+ lifted = _native.activity_lift_tail(tail, opener_flags, open_turn=bool(turns))
340
+ first = len(turns) - 1 if lifted["continued"] else len(turns)
341
+ grown = [
342
+ lift_turn(self._session_id, first + offset, skeleton, tail, self._results)
343
+ for offset, skeleton in enumerate(lifted["turns"])
344
+ ]
345
+ if lifted["continued"]:
346
+ turns[-1] = continued(turns[-1], grown[0])
347
+ turns.extend(grown[1:])
348
+ else:
349
+ turns.extend(grown)
350
+ repairs: dict[int, dict[int, UserEvent]] = {}
351
+ for tool_use_id, result_idx in lifted["results"]:
352
+ for turn_index, position in self._uses.get(tool_use_id, ()):
353
+ repairs.setdefault(turn_index, {})[position] = tail[result_idx]
354
+ self._results[tool_use_id] = tail[result_idx]
355
+ for turn_index, at in repairs.items():
356
+ turns[turn_index] = repaired(turns[turn_index], at)
357
+ for turn in grown:
358
+ base = len(turns[turn.index].tool_uses) - len(turn.tool_uses)
359
+ for position, use in enumerate(turn.tool_uses, base):
360
+ self._uses[use.ref.tool_use_id] = (*self._uses.get(use.ref.tool_use_id, ()), (turn.index, position))
361
+ self._activity = SessionActivity(session_id=self._session_id, turns=tuple(turns))
362
+ return self._activity
363
+
364
+
365
+ def lift_turn(
366
+ session_id: SessionId,
367
+ index: int,
368
+ skeleton: dict[str, Any],
369
+ evs: Sequence[TranscriptEvent],
370
+ prior_results: Mapping[ToolUseId, UserEvent],
371
+ ) -> Turn:
372
+ events = tuple(evs[skeleton["start"] : skeleton["end"]])
373
+ calls = [
374
+ (event, block)
375
+ for event in events
376
+ if isinstance(event, AssistantEvent)
377
+ for block in event.blocks
378
+ if isinstance(block, ToolUseBlock)
379
+ ]
380
+ started_idx = skeleton["started_idx"]
381
+ ended_idx = skeleton["ended_idx"]
382
+ return Turn(
383
+ index=index,
384
+ prompt=skeleton["prompt"],
385
+ started_at=None if started_idx is None else evs[started_idx].meta.timestamp,
386
+ ended_at=None if ended_idx is None else evs[ended_idx].meta.timestamp,
387
+ events=events,
388
+ tool_uses=tuple(
389
+ lift_tool_use(
390
+ session_id,
391
+ index,
392
+ event,
393
+ block,
394
+ prior_results.get(block.id) if (result_idx := use["result_event_idx"]) is None else evs[result_idx],
395
+ )
396
+ for (event, block), use in zip(calls, skeleton["tool_uses"], strict=True)
397
+ ),
398
+ )
399
+
400
+
401
+ def lift_tool_use(
402
+ session_id: SessionId, turn_index: int, event: AssistantEvent, block: ToolUseBlock, result_event: UserEvent | None
403
+ ) -> ToolUse:
404
+ call = parse_tool_call(block.name, block.input, on_error="other")
405
+ return ToolUse(
406
+ ref=EventRef(session_id, event.meta.uuid, block.id),
407
+ call=call,
408
+ result=None if result_event is None else result_block(result_event, block.id),
409
+ result_ts=None if result_event is None else result_event.meta.timestamp,
410
+ edits=edits_of(call),
411
+ turn_index=turn_index,
412
+ ts=event.meta.timestamp,
413
+ )
414
+
415
+
416
+ def result_block(event: UserEvent, tool_use_id: ToolUseId) -> ToolResultBlock:
417
+ return next(
418
+ candidate
419
+ for candidate in reversed(event.blocks)
420
+ if isinstance(candidate, ToolResultBlock) and candidate.tool_use_id == tool_use_id
421
+ )
422
+
423
+
424
+ def continued(turn: Turn, tail: Turn) -> Turn:
425
+ return replace(
426
+ turn,
427
+ started_at=tail.started_at if turn.started_at is None else turn.started_at,
428
+ ended_at=turn.ended_at if tail.ended_at is None else tail.ended_at,
429
+ events=turn.events + tail.events,
430
+ tool_uses=turn.tool_uses + tail.tool_uses,
431
+ )
432
+
433
+
434
+ def repaired(turn: Turn, at: Mapping[int, UserEvent]) -> Turn:
435
+ uses = list(turn.tool_uses)
436
+ for position, event in at.items():
437
+ uses[position] = replace(
438
+ uses[position],
439
+ result=result_block(event, uses[position].ref.tool_use_id),
440
+ result_ts=event.meta.timestamp,
441
+ )
442
+ return replace(turn, tool_uses=tuple(uses))
443
+
444
+
329
445
  def hunk_overlap(a: Hunk, b: Hunk) -> float:
330
446
  """The fraction of ``a.new``'s non-empty lines present in ``b.old``.
331
447