Module: Agent::Sessions
- Defined in:
- lib/agent/sessions.rb,
lib/agent/sessions/cli.rb,
lib/agent/sessions/node.rb,
lib/agent/sessions/part.rb,
lib/agent/sessions/audit.rb,
lib/agent/sessions/check.rb,
lib/agent/sessions/error.rb,
lib/agent/sessions/store.rb,
lib/agent/sessions/usage.rb,
lib/agent/sessions/sqlite.rb,
lib/agent/sessions/message.rb,
lib/agent/sessions/session.rb,
lib/agent/sessions/version.rb,
lib/agent/sessions/location.rb,
lib/agent/sessions/compaction.rb,
lib/agent/sessions/readers/pi.rb,
lib/agent/sessions/adapters/pi.rb,
lib/agent/sessions/readers/amp.rb,
lib/agent/sessions/adapters/amp.rb,
lib/agent/sessions/env_override.rb,
lib/agent/sessions/readers/base.rb,
lib/agent/sessions/readers/grok.rb,
lib/agent/sessions/readers/qwen.rb,
lib/agent/sessions/adapters/base.rb,
lib/agent/sessions/adapters/grok.rb,
lib/agent/sessions/adapters/qwen.rb,
lib/agent/sessions/readers/codex.rb,
lib/agent/sessions/unknown_agent.rb,
lib/agent/sessions/adapters/codex.rb,
lib/agent/sessions/home_expansion.rb,
lib/agent/sessions/readers/claude.rb,
lib/agent/sessions/readers/gemini.rb,
lib/agent/sessions/adapters/claude.rb,
lib/agent/sessions/adapters/cursor.rb,
lib/agent/sessions/adapters/gemini.rb,
lib/agent/sessions/readers/copilot.rb,
lib/agent/sessions/adapters/copilot.rb,
lib/agent/sessions/readers/opencode.rb,
lib/agent/sessions/unreadable_store.rb,
lib/agent/sessions/adapters/opencode.rb,
lib/agent/sessions/missing_dependency.rb,
lib/agent/sessions/unsupported_format.rb,
lib/agent/sessions/adapters/cursor_ide.rb,
lib/agent/sessions/adapters/enumeration.rb
Defined Under Namespace
Modules: Adapters, HomeExpansion, Readers, Sqlite Classes: Audit, CLI, Check, Compaction, EnvOverride, Error, Location, Message, MissingDependency, Node, Part, Session, Store, UnknownAgent, UnreadableStore, UnsupportedFormat, Usage
Constant Summary collapse
- STALE_AFTER_DAYS =
90- VERIFIED_ON =
The oldest verified_on among the built-in adapters. A claim about somebody else's software is only as current as its weakest link, so this is the honest answer to "when was this last known to be true".
registry.values.map(&:verified_on_date).min
- VERSION =
"0.3.1"
Class Method Summary collapse
- .agents ⇒ Object
- .all(env: ENV) ⇒ Object
- .audit(env: ENV) ⇒ Object
- .doctor(agent = nil, env: ENV, today: Date.today) ⇒ Object
-
.for_project(dir, env: ENV, agents: nil) ⇒ Object
One project across every agent (or the agents: subset), lazily: adapters earlier in the sweep satisfy
first(n)without the later ones ever being asked. - .installed(env: ENV) ⇒ Object
- .locate(agent, env: ENV) ⇒ Object
-
.projects(agent, env: ENV) ⇒ Object
Eager, unlike sessions/for_project: project_paths already reads every session to answer (design doc section 7 — the on-disk encodings are lossy, so the recorded cwd is the only reliable source), sorts, and dedupes, so a lazy return type here would promise a laziness the work underneath cannot honor.
-
.read(session, **options) ⇒ Object
Layer 3.
-
.register(adapter_class) ⇒ Object
Re-registering a name deliberately replaces it, so a consumer can ship a corrected adapter for an agent whose layout moved before the gem catches up.
- .registry ⇒ Object
-
.sessions(agent, env: ENV, since: nil) ⇒ Object
Lazy: consuming N sessions stats N files, never more.
-
.unresolved_project_count(agent, env: ENV) ⇒ Object
Companion to
projects/project_paths, which both exclude a session whose project could not be resolved rather than counting it (design doc section 7) — so "this agent genuinely records no projects" and "this agent's project resolution is broken" read identically from the outside. - .verify(agent = nil, env: ENV) ⇒ Object
Class Method Details
.agents ⇒ Object
34 |
# File 'lib/agent/sessions.rb', line 34 def agents = registry.keys |
.all(env: ENV) ⇒ Object
40 41 42 |
# File 'lib/agent/sessions.rb', line 40 def all(env: ENV) registry.keys.map { |agent| locate(agent, env: env) } end |
.audit(env: ENV) ⇒ Object
169 170 171 |
# File 'lib/agent/sessions.rb', line 169 def audit(env: ENV) Audit.new(all(env: env), env: env).report end |
.doctor(agent = nil, env: ENV, today: Date.today) ⇒ Object
154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/agent/sessions.rb', line 154 def doctor(agent = nil, env: ENV, today: Date.today) targets = agent ? [adapter_for(agent)] : registry.values staleness = targets.map do |klass| age = (today - klass.verified_on_date).to_i if age > STALE_AFTER_DAYS Check.new(agent: klass.agent_name, status: :drift, claim: "verified within #{STALE_AFTER_DAYS} days", detail: "last verified #{klass.verified_on_date} (#{age} days ago)") else Check.new(agent: klass.agent_name, status: :pass, claim: "verified within #{STALE_AFTER_DAYS} days", detail: "last verified #{klass.verified_on_date}") end end verify(agent, env: env) + staleness end |
.for_project(dir, env: ENV, agents: nil) ⇒ Object
One project across every agent (or the agents: subset), lazily: adapters
earlier in the sweep satisfy first(n) without the later ones ever being
asked. Within one adapter, though, laziness cannot skip non-matching
sessions — sessions_for_project must still stat and check each candidate
to know it does not match, so an adapter with zero matches costs a full
scan of its store before the sweep moves on. True of the six Base-driven
adapters; opencode pushes the filter into SQL (WHERE directory = ?) and
stats nothing.
Deliberately does NOT rescue MissingDependency or UnreadableStore: opencode
without the sqlite3 gem, or with a corrupt/locked database, raises. Since
flat_map is lazy, that raise surfaces only once enumeration reaches the
failing adapter — possibly after other agents' sessions have already been
yielded to the caller mid-iteration, and possibly not at all if first(n)
is satisfied first. Silently omitting an agent's sessions is this gem's
worst failure mode (design doc decision 11), so this method never trades
a raised, attributable error for a quietly incomplete list. A caller that
wants the sweep to survive one bad agent should rescue per call, e.g. by
driving agents: itself and catching around each adapter; a caller who
just wants to route around a known-bad agent can pass agents: naming
every registered agent except it. The CLI (Task 10) does the former,
turning the same exceptions into per-agent "skipped" lines instead of one
failed sweep. Note a rescue cannot resume this enumerator: re-calling each
after a raise re-raises from the same adapter. The only recovery is a
fresh call with a narrower agents:.
Adapters are resolved eagerly, so an unknown name in agents: raises here
rather than mid-sweep. The deferral above is about DATA conditions, where
the raise carries information about a store; a typo'd agent symbol is a
programmer error knowable before any I/O, and agents: [:claude, :nope]
otherwise hands back Claude's sessions and then crashes. Base#initialize
only stores @env, so constructing all seven up front costs nothing, and
the sweep stays lazy — first(n) still stops at the first matching adapter.
95 96 97 98 99 |
# File 'lib/agent/sessions.rb', line 95 def for_project(dir, env: ENV, agents: nil) dir = File.(dir) adapters = (agents || registry.keys).map { |name| adapter_for(name).new(env: env) } adapters.lazy.flat_map { |adapter| adapter.sessions_for_project(dir) } end |
.installed(env: ENV) ⇒ Object
44 45 46 |
# File 'lib/agent/sessions.rb', line 44 def installed(env: ENV) all(env: env).select(&:installed?) end |
.locate(agent, env: ENV) ⇒ Object
36 37 38 |
# File 'lib/agent/sessions.rb', line 36 def locate(agent, env: ENV) adapter_for(agent).new(env: env).locate end |
.projects(agent, env: ENV) ⇒ Object
Eager, unlike sessions/for_project: project_paths already reads every session to answer (design doc section 7 — the on-disk encodings are lossy, so the recorded cwd is the only reliable source), sorts, and dedupes, so a lazy return type here would promise a laziness the work underneath cannot honor. Returns a plain, already-sorted Array. Raises MissingDependency or UnreadableStore for opencode, as sessions does.
107 108 109 |
# File 'lib/agent/sessions.rb', line 107 def projects(agent, env: ENV) adapter_for(agent).new(env: env).project_paths end |
.read(session, **options) ⇒ Object
Layer 3. Takes a Session (from sessions, for_project), not an agent
name, because reading is per-session — the adapter comes from the session
itself. Raises UnsupportedFormat for an agent with no reader yet, rather
than returning a reader that yields nothing: "this gem cannot read that
format" and "that session has no messages" must never look alike.
include_events: true adds the agent's UI-level records to the stream where an adapter has them. They are excluded by default because they are bookkeeping, not conversation, and they outnumber real messages.
139 140 141 142 143 144 145 146 147 |
# File 'lib/agent/sessions.rb', line 139 def read(session, **) klass = adapter_for(session.agent).reader_class unless klass raise UnsupportedFormat, "no reader for #{session.agent} yet; Session#fidelity says #{session.fidelity}" end klass.new(session, **) end |
.register(adapter_class) ⇒ Object
Re-registering a name deliberately replaces it, so a consumer can ship a corrected adapter for an agent whose layout moved before the gem catches up.
23 24 25 26 27 28 |
# File 'lib/agent/sessions.rb', line 23 def register(adapter_class) name = adapter_class.agent_name raise Error, "#{adapter_class.inspect} declares no agent name" if name.nil? registry[name] = adapter_class end |
.registry ⇒ Object
30 31 32 |
# File 'lib/agent/sessions.rb', line 30 def registry @registry ||= {} end |
.sessions(agent, env: ENV, since: nil) ⇒ Object
Lazy: consuming N sessions stats N files, never more. since, when given,
must be a Time (or anything Time#>= accepts) — comparing updated_at (always
a Time; every adapter populates it, from mtime or store metadata) against a
Date, Integer, or String raises ArgumentError("comparison of Time with ...
failed"), which already names the mistake, so no extra guard is added here.
That raise happens on enumeration, not on this call, because the filter
itself is lazy — sessions(:x, since: bad).first(1) can raise from inside
first, not from this line.
Raises MissingDependency or UnreadableStore for opencode under the same
conditions described on for_project below.
58 59 60 61 |
# File 'lib/agent/sessions.rb', line 58 def sessions(agent, env: ENV, since: nil) list = adapter_for(agent).new(env: env).sessions since ? list.select { |session| session.updated_at >= since } : list end |
.unresolved_project_count(agent, env: ENV) ⇒ Object
Companion to projects/project_paths, which both exclude a session
whose project could not be resolved rather than counting it (design doc
section 7) — so "this agent genuinely records no projects" and "this
agent's project resolution is broken" read identically from the
outside. Three of seven adapters can legitimately return a nil
project_path (Amp threads with no trees, cursor_ide by design, pi
whenever its unverified header assumption is wrong); this counts it for
any of them, uniformly, using only the public sessions enumerator —
no adapter needs to know this exists. Eager and a second full sweep of
the store, same cost class as projects itself, so it is opt-in
(called by the CLI only under list --project, never under plain
list) rather than folded into projects' own return value, which is
a documented, tested plain Array and would otherwise need a shape
change to carry both numbers.
Raises MissingDependency or UnreadableStore for opencode, as sessions does.
126 127 128 |
# File 'lib/agent/sessions.rb', line 126 def unresolved_project_count(agent, env: ENV) adapter_for(agent).new(env: env).sessions.count { |session| session.project_path.nil? } end |
.verify(agent = nil, env: ENV) ⇒ Object
149 150 151 152 |
# File 'lib/agent/sessions.rb', line 149 def verify(agent = nil, env: ENV) targets = agent ? [adapter_for(agent)] : registry.values targets.flat_map { |klass| klass.new(env: env).verify } end |