cc-transcript 14.19.0__tar.gz → 14.20.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.19.0 → cc_transcript-14.20.0}/PKG-INFO +1 -1
  2. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/_native.pyi +5 -0
  3. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/query.py +17 -1
  4. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/pyproject.toml +1 -1
  5. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/context.rs +13 -4
  6. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/parse.rs +1 -0
  7. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/render.rs +56 -17
  8. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/types.rs +1 -0
  9. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/views/attachment.rs +9 -1
  10. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/Cargo.lock +0 -0
  11. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/Cargo.toml +0 -0
  12. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/LICENSE +0 -0
  13. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/README.md +0 -0
  14. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/__init__.py +0 -0
  15. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/__init__.pyi +0 -0
  16. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/activity.py +0 -0
  17. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/activity_probe.py +0 -0
  18. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/builders.py +0 -0
  19. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/codex.py +0 -0
  20. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/command.py +0 -0
  21. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/context.py +0 -0
  22. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/corrections.py +0 -0
  23. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/cost.py +0 -0
  24. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/decision_store.py +0 -0
  25. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/decisions.py +0 -0
  26. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/discovery.py +0 -0
  27. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/disktruth.py +0 -0
  28. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/evidence.py +0 -0
  29. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/extract/__init__.py +0 -0
  30. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/extract/correct.py +0 -0
  31. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/facts.py +0 -0
  32. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/filterspec.py +0 -0
  33. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/heartbeats.py +0 -0
  34. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/ids.py +0 -0
  35. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/judge/__init__.py +0 -0
  36. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/judge/llm.py +0 -0
  37. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/judge/similar.py +0 -0
  38. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/judge/verdicts.py +0 -0
  39. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/ledger.py +0 -0
  40. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/literals.py +0 -0
  41. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/mining/__init__.py +0 -0
  42. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/mining/candidates.py +0 -0
  43. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/mining/confidence.py +0 -0
  44. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/mining/engine.py +0 -0
  45. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/mining/formats.py +0 -0
  46. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/mining/sampling.py +0 -0
  47. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/mining/signals.py +0 -0
  48. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/mining/sourcekind.py +0 -0
  49. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/mining/spec.py +0 -0
  50. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/mining/store.py +0 -0
  51. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/models.py +0 -0
  52. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/nlp.py +0 -0
  53. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/notifications.py +0 -0
  54. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/parser.py +0 -0
  55. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/py.typed +0 -0
  56. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/render.py +0 -0
  57. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/sentiment/__init__.py +0 -0
  58. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/sentiment/buckets.py +0 -0
  59. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/sentiment/data/afinn-en-165.tsv +0 -0
  60. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/sentiment/data/domain_overrides.tsv +0 -0
  61. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/sentiment/data/en-ewt.udpipe +0 -0
  62. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/sentiment/engine.py +0 -0
  63. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/sentiment/lexicon.py +0 -0
  64. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/sentiment/scorespec.py +0 -0
  65. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/synthetic.py +0 -0
  66. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/tools.py +0 -0
  67. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/cc_transcript/watch.py +0 -0
  68. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/Cargo.toml +0 -0
  69. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/commands/blame.rs +0 -0
  70. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/commands/corrections.rs +0 -0
  71. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/commands/digest.rs +0 -0
  72. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/commands/facts.rs +0 -0
  73. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/commands/grep.rs +0 -0
  74. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/commands/list.rs +0 -0
  75. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/commands/mod.rs +0 -0
  76. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/commands/scratchpad.rs +0 -0
  77. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/commands/show.rs +0 -0
  78. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/commands/slice.rs +0 -0
  79. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/commands/stats.rs +0 -0
  80. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/commands/watch.rs +0 -0
  81. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/lib.rs +0 -0
  82. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/main.rs +0 -0
  83. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/output.rs +0 -0
  84. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/target.rs +0 -0
  85. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/cli/src/timearg.rs +0 -0
  86. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/Cargo.toml +0 -0
  87. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/benches/parse.rs +0 -0
  88. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/benches/render.rs +0 -0
  89. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/benches/toolcall.rs +0 -0
  90. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/activity.rs +0 -0
  91. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/actor.rs +0 -0
  92. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/blame.rs +0 -0
  93. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/buckets.rs +0 -0
  94. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/codex/discovery.rs +0 -0
  95. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/codex/lower.rs +0 -0
  96. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/codex/mod.rs +0 -0
  97. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/codex/parse.rs +0 -0
  98. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/codex/probe.rs +0 -0
  99. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/codex/protocol.rs +0 -0
  100. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/codex/types.rs +0 -0
  101. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/command.rs +0 -0
  102. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/corrections.rs +0 -0
  103. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/cost.rs +0 -0
  104. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/discovery.rs +0 -0
  105. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/facts.rs +0 -0
  106. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/feedback.rs +0 -0
  107. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/filter.rs +0 -0
  108. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/gateway.rs +0 -0
  109. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/ids.rs +0 -0
  110. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/judge.rs +0 -0
  111. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/lib.rs +0 -0
  112. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/literals/command.rs +0 -0
  113. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/literals/corrections.rs +0 -0
  114. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/literals/feedback.rs +0 -0
  115. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/literals/mining.rs +0 -0
  116. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/literals/mod.rs +0 -0
  117. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/literals/protocol.rs +0 -0
  118. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/notifications.rs +0 -0
  119. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/protocol.rs +0 -0
  120. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/pystr.rs +0 -0
  121. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/query.rs +0 -0
  122. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/rng.rs +0 -0
  123. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/schema.rs +0 -0
  124. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/sqlite.rs +0 -0
  125. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/toolcall.rs +0 -0
  126. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/value.rs +0 -0
  127. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/core/src/watch.rs +0 -0
  128. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/Cargo.toml +0 -0
  129. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/build.rs +0 -0
  130. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/data/command_prefix_pins.tsv +0 -0
  131. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/actor_bridge.rs +0 -0
  132. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/bin/stub_gen.rs +0 -0
  133. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/codex.rs +0 -0
  134. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/command.rs +0 -0
  135. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/context.rs +0 -0
  136. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/corrections.rs +0 -0
  137. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/discovery.rs +0 -0
  138. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/feedback.rs +0 -0
  139. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/judge.rs +0 -0
  140. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/lexicon.rs +0 -0
  141. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/lib.rs +0 -0
  142. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/mining.rs +0 -0
  143. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/nlp.rs +0 -0
  144. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/python.rs +0 -0
  145. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/render.rs +0 -0
  146. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/score.rs +0 -0
  147. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/sqlite.rs +0 -0
  148. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/toolcall.rs +0 -0
  149. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/views/blocks.rs +0 -0
  150. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/views/command.rs +0 -0
  151. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/views/convert.rs +0 -0
  152. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/views/dunder.rs +0 -0
  153. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/views/events.rs +0 -0
  154. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/views/meta.rs +0 -0
  155. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/views/mod.rs +0 -0
  156. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/views/print.rs +0 -0
  157. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/views/store.rs +0 -0
  158. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/views/system.rs +0 -0
  159. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/views/toolcall.rs +0 -0
  160. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/views/toolresult.rs +0 -0
  161. {cc_transcript-14.19.0 → cc_transcript-14.20.0}/rust/crates/py/src/views/transcript.rs +0 -0
  162. {cc_transcript-14.19.0 → cc_transcript-14.20.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.19.0
3
+ Version: 14.20.0
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -1498,12 +1498,17 @@ class QueuedCommand:
1498
1498
  plain-string prompt (e.g. an image-paste payload).
1499
1499
  command_mode: How the command was queued, e.g. ``prompt`` or
1500
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.
1501
1504
  """
1502
1505
  __match_args__: typing.ClassVar[tuple[str, ...]]
1503
1506
  @property
1504
1507
  def prompt(self) -> typing.Optional[builtins.str]: ...
1505
1508
  @property
1506
1509
  def command_mode(self) -> typing.Optional[builtins.str]: ...
1510
+ @property
1511
+ def origin(self) -> typing.Optional[builtins.str]: ...
1507
1512
 
1508
1513
  @typing.final
1509
1514
  class ReadCall(ToolCallBase):
@@ -26,7 +26,7 @@ from cc_transcript.activity import ActivityLift, SessionActivity, Turn, event_st
26
26
  from cc_transcript.discovery import TranscriptExpiredError, resolve, subagent_paths, subagent_transcripts
27
27
  from cc_transcript.filterspec import event_meta, session_id_of
28
28
  from cc_transcript.ids import SessionId, ToolUseId
29
- from cc_transcript.models import AssistantEvent, SystemEvent, ToolResultBlock, UserEvent
29
+ from cc_transcript.models import AssistantEvent, AttachmentEvent, QueuedCommand, SystemEvent, ToolResultBlock, UserEvent
30
30
  from cc_transcript.notifications import Notifications
31
31
  from cc_transcript.parser import parse
32
32
  from cc_transcript.tools import (
@@ -642,6 +642,16 @@ class Session:
642
642
  """The window's last ``n`` events."""
643
643
  return windowed(self, max(len(self) - n, 0), len(self))
644
644
 
645
+ def recent_messages(self, n: int) -> Session:
646
+ """The window reaching back over its last ``n`` messages, for positive ``n``.
647
+
648
+ A message is a user or assistant event or a queued command. The hook,
649
+ reminder, system and mode events between messages ride along without
650
+ counting, so harness noise never crowds the conversation out of the window.
651
+ """
652
+ messages = [index for index, event in enumerate(self.events) if is_message(event)]
653
+ return windowed(self, messages[-n] if n <= len(messages) else 0, len(self))
654
+
645
655
  @property
646
656
  def current_turn(self) -> Session:
647
657
  """The one-turn view of the window's last turn."""
@@ -960,6 +970,12 @@ class DeepView:
960
970
  return iter(self.sessions)
961
971
 
962
972
 
973
+ def is_message(event: TranscriptEvent) -> bool:
974
+ return isinstance(event, UserEvent | AssistantEvent) or (
975
+ isinstance(event, AttachmentEvent) and isinstance(event.detail, QueuedCommand)
976
+ )
977
+
978
+
963
979
  def windowed(session: Session, start: int, stop: int) -> Session:
964
980
  turns: list[Turn] = []
965
981
  base = 0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cc-transcript"
3
- version = "14.19.0" # informational — releases stamp the version from the git tag
3
+ version = "14.20.0" # informational — releases stamp the version from the git tag
4
4
  description = "Grep every Claude Code session you've ever run."
5
5
  readme = "README.md"
6
6
  license = "PolyForm-Noncommercial-1.0.0"
@@ -11,7 +11,7 @@ use sonic_rs::{JsonContainerTrait, JsonValueTrait, Value};
11
11
  use crate::activity::{LiftedSession, ToolUse, Turn};
12
12
  use crate::ids::{encode_string, tool_digest, EventRef};
13
13
  use crate::parse::parse_questions;
14
- use crate::render::{clip, render_tool_call, render_turn, Budget};
14
+ use crate::render::{clip, human_queued_prompt, render_tool_call, render_turn, Budget};
15
15
  use crate::toolcall::{parse_tool_call, parse_tool_result, ToolResult};
16
16
  use crate::types::{ContentBlock, Entry, Question, ToolUseBlock};
17
17
  use crate::value::{field, field_bool, field_str};
@@ -19,7 +19,7 @@ use crate::value::{field, field_bool, field_str};
19
19
  pub const SCHEMA: &str = "cc-transcript.context/2";
20
20
  pub const PREVIEW_SCHEMA: &str = "cc-transcript.preview/1";
21
21
  pub const SUMMARY_LABEL: &str = "[summary fidelity — transcript unavailable]";
22
- const ASK_USER_QUESTION: &str = "AskUserQuestion";
22
+ pub(crate) const ASK_USER_QUESTION: &str = "AskUserQuestion";
23
23
 
24
24
  /// One structured preview part of a turn (context.py `Preview`): typed text, a tool
25
25
  /// call reduced to name + digest + summary, or an AskUserQuestion round with its
@@ -549,8 +549,17 @@ fn build_previews(turn: &Turn, budget: &Budget) -> Vec<Preview> {
549
549
  }
550
550
  let mut uses = turn.tool_uses.iter();
551
551
  for &event in &turn.events {
552
- let Entry::Assistant(assistant) = event else {
553
- continue;
552
+ let assistant = match event {
553
+ Entry::Assistant(assistant) => assistant,
554
+ Entry::Attachment(attachment) => {
555
+ if let Some(prompt) = human_queued_prompt(&attachment.detail) {
556
+ parts.push(Preview::Text {
557
+ text: clip(prompt, budget.turn_chars),
558
+ });
559
+ }
560
+ continue;
561
+ }
562
+ _ => continue,
554
563
  };
555
564
  for block in &assistant.blocks {
556
565
  match block {
@@ -478,6 +478,7 @@ fn parse_attachment_detail(data: &Value) -> AttachmentDetail {
478
478
  Some("queued_command") => AttachmentDetail::QueuedCommand(QueuedCommand {
479
479
  prompt: opt_str(att, "prompt"),
480
480
  command_mode: opt_str(att, "commandMode"),
481
+ origin: field(att, "origin").and_then(|origin| opt_str(origin, "kind")),
481
482
  }),
482
483
  Some("deferred_tools_delta") => AttachmentDetail::DeferredToolsDelta(DeferredToolsDelta {
483
484
  added_names: str_array(att, "addedNames"),
@@ -1,13 +1,14 @@
1
1
  //! The one renderer, ported from `cc_transcript/render.py` — every cut happens here,
2
2
  //! under a [`Budget`]. Parity notes on the individual helpers.
3
3
 
4
- use std::collections::HashMap;
4
+ use std::collections::{HashMap, HashSet};
5
5
 
6
6
  use chrono::{DateTime, FixedOffset};
7
7
  use sonic_rs::{JsonContainerTrait, JsonType, JsonValueTrait, Value};
8
8
 
9
9
  use crate::activity::Turn;
10
10
  use crate::blame::{SessionWrites, Verdict};
11
+ use crate::context::ASK_USER_QUESTION;
11
12
  #[cfg(feature = "command")]
12
13
  use crate::facts::{McpServerSummary, ToolFact};
13
14
  use crate::filter::{entry_text, event_kind};
@@ -432,36 +433,73 @@ pub fn render_tool_call(call: &ToolCall, budget: &Budget) -> String {
432
433
  }
433
434
  }
434
435
 
435
- /// Render one turn — the prompt, assistant prose, and every tool call, in order
436
- /// (render.py render_turn). Prose clips to `budget.turn_chars`; each tool call
437
- /// renders via [`render_tool_call`] under `budget.tool_chars`.
436
+ /// Render one turn — the prompt, the user's mid-turn messages, assistant prose,
437
+ /// every tool call, and the user's AskUserQuestion answers, in order (render.py
438
+ /// render_turn). Prose clips to `budget.turn_chars`; each tool call and answer
439
+ /// renders under `budget.tool_chars`.
438
440
  pub fn render_turn(turn: &Turn, budget: &Budget) -> String {
439
441
  let mut parts: Vec<String> = Vec::new();
440
442
  if !turn.prompt.is_empty() {
441
443
  parts.push(format!("user: {}", clip(&turn.prompt, budget.turn_chars)));
442
444
  }
445
+ let mut questions: HashSet<&str> = HashSet::new();
443
446
  for &event in &turn.events {
444
- let Entry::Assistant(assistant) = event else {
445
- continue;
446
- };
447
- for block in &assistant.blocks {
448
- match block {
449
- ContentBlock::Text(text) if !pystr::strip(text).is_empty() => {
450
- parts.push(format!("assistant: {}", clip(text, budget.turn_chars)));
447
+ match event {
448
+ Entry::Assistant(assistant) => {
449
+ for block in &assistant.blocks {
450
+ match block {
451
+ ContentBlock::Text(text) if !pystr::strip(text).is_empty() => {
452
+ parts.push(format!("assistant: {}", clip(text, budget.turn_chars)));
453
+ }
454
+ ContentBlock::ToolUse(tool_use) => {
455
+ if tool_use.name == ASK_USER_QUESTION {
456
+ questions.insert(&tool_use.id);
457
+ }
458
+ parts.push(render_tool_call(
459
+ &parse_tool_call(&tool_use.name, &tool_use.input),
460
+ budget,
461
+ ));
462
+ }
463
+ _ => {}
464
+ }
451
465
  }
452
- ContentBlock::ToolUse(tool_use) => {
453
- parts.push(render_tool_call(
454
- &parse_tool_call(&tool_use.name, &tool_use.input),
455
- budget,
456
- ));
466
+ }
467
+ Entry::User(user) => {
468
+ parts.extend(user.blocks().iter().filter_map(|block| match block {
469
+ ContentBlock::ToolResult(result)
470
+ if !result.is_error && questions.contains(result.tool_use_id.as_str()) =>
471
+ {
472
+ Some(format!(
473
+ "user answered: {}",
474
+ clip(&result.content, budget.tool_chars)
475
+ ))
476
+ }
477
+ _ => None,
478
+ }));
479
+ }
480
+ Entry::Attachment(attachment) => {
481
+ if let Some(prompt) = human_queued_prompt(&attachment.detail) {
482
+ parts.push(format!("user: {}", clip(prompt, budget.turn_chars)));
457
483
  }
458
- _ => {}
459
484
  }
485
+ _ => {}
460
486
  }
461
487
  }
462
488
  parts.join("\n")
463
489
  }
464
490
 
491
+ pub(crate) fn human_queued_prompt(detail: &AttachmentDetail) -> Option<&str> {
492
+ match detail {
493
+ AttachmentDetail::QueuedCommand(queued)
494
+ if queued.command_mode.as_deref() == Some("prompt")
495
+ && queued.origin.as_deref() == Some("human") =>
496
+ {
497
+ queued.prompt.as_deref()
498
+ }
499
+ _ => None,
500
+ }
501
+ }
502
+
465
503
  /// One compact `index tag time payload [uuid]` line (render.py compact_line).
466
504
  pub fn compact_line(
467
505
  index: usize,
@@ -1802,6 +1840,7 @@ fn attachment_detail_json(detail: &AttachmentDetail) -> Json {
1802
1840
  AttachmentDetail::QueuedCommand(q) => obj(vec![
1803
1841
  ("prompt", opt_str(&q.prompt)),
1804
1842
  ("command_mode", opt_str(&q.command_mode)),
1843
+ ("origin", opt_str(&q.origin)),
1805
1844
  ]),
1806
1845
  AttachmentDetail::DeferredToolsDelta(delta) => obj(vec![
1807
1846
  ("added_count", Json::UInt(delta.added_names.len() as u64)),
@@ -357,6 +357,7 @@ pub struct AsyncHookResponse {
357
357
  pub struct QueuedCommand {
358
358
  pub prompt: Option<String>,
359
359
  pub command_mode: Option<String>,
360
+ pub origin: Option<String>,
360
361
  }
361
362
 
362
363
  #[derive(Debug)]
@@ -463,6 +463,9 @@ view_dunders!(
463
463
  /// plain-string prompt (e.g. an image-paste payload).
464
464
  /// command_mode: How the command was queued, e.g. ``prompt`` or
465
465
  /// ``task-notification``, or None.
466
+ /// origin: Who sent a ``prompt``-mode command: ``human`` for the user,
467
+ /// ``peer`` for another agent or session, ``channel`` for an MCP
468
+ /// channel event; None when the transcript does not record it.
466
469
  #[pyo3_stub_gen::derive::gen_stub_pyclass]
467
470
  #[pyclass(name = "QueuedCommand", module = "cc_transcript.models", frozen)]
468
471
  pub(crate) struct QueuedCommandView {
@@ -490,12 +493,17 @@ impl QueuedCommandView {
490
493
  fn command_mode(&self, _py: Python<'_>) -> PyResult<Option<String>> {
491
494
  Ok(self.queued_command().command_mode.clone())
492
495
  }
496
+
497
+ #[getter]
498
+ fn origin(&self, _py: Python<'_>) -> PyResult<Option<String>> {
499
+ Ok(self.queued_command().origin.clone())
500
+ }
493
501
  }
494
502
 
495
503
  view_dunders!(
496
504
  QueuedCommandView,
497
505
  "QueuedCommand",
498
- fields = [prompt, command_mode]
506
+ fields = [prompt, command_mode, origin]
499
507
  );
500
508
 
501
509
  /// Tools added to and removed from the deferred tool inventory.
File without changes