Class: AgentsControl::Transcript

Inherits:
Object
  • Object
show all
Defined in:
lib/agents_control/transcript.rb

Overview

The tail of a conversation with an agent.

Read from the JSONL file whose path the agent sends with every hook. This beats scraping text off the screen for three reasons: it works the same for a terminal tab and for a VS Code session, which has no screen at all; it gives structure instead of ANSI noise; and it isn't limited to what fit in the visible area.

Constant Summary collapse

TAIL_BYTES =

The file grows without bound, and only the end matters. Read from the end in blocks, so a multi-megabyte history never has to be pulled fully into memory.

256 * 1024
PROJECTS =
File.expand_path("~/.claude/projects")

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ Transcript

Returns a new instance of Transcript.



45
46
47
# File 'lib/agents_control/transcript.rb', line 45

def initialize(path)
  @path = path
end

Class Method Details

.for_cwd(cwd, root: PROJECTS) ⇒ Object

The transcript of a tab the hooks never sent anything about.

Claude Code lays out histories in directories named after the working path, with / and _ replaced by -. If that scheme ever changes, this method just returns an empty transcript instead of breaking its caller.



28
29
30
31
32
33
# File 'lib/agents_control/transcript.rb', line 28

def for_cwd(cwd, root: PROJECTS)
  return new(nil) if cwd.to_s.empty?

  directory = File.join(root, slug(cwd))
  new(newest_in(directory))
end

.slug(cwd) ⇒ Object



35
# File 'lib/agents_control/transcript.rb', line 35

def slug(cwd) = cwd.to_s.tr("/_", "--")

Instance Method Details

#exists?Boolean

Returns:

  • (Boolean)


49
# File 'lib/agents_control/transcript.rb', line 49

def exists? = @path && File.exist?(@path)

#last(count = 6) ⇒ Object

The last N messages as [text:].



52
53
54
55
56
57
# File 'lib/agents_control/transcript.rb', line 52

def last(count = 6)
  return [] unless exists?

  messages = parse(tail)
  messages.last(count)
end

#render(count = 6) ⇒ Object

Ready-to-send text, in full — splitting across multiple Telegram messages, if needed, is the caller's job (Router#say_chunked).



61
62
63
64
65
66
# File 'lib/agents_control/transcript.rb', line 61

def render(count = 6)
  entries = last(count)
  return "The conversation is empty." if entries.empty?

  entries.map { |entry| "#{prefix(entry[:role])} #{entry[:text]}" }.join("\n\n")
end