agent_session_context 1.0.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +43 -0
- data/LICENSE.txt +21 -0
- data/README.md +298 -0
- data/exe/agent-session-context +6 -0
- data/lib/agent/session_context/builder.rb +150 -0
- data/lib/agent/session_context/cli/options.rb +214 -0
- data/lib/agent/session_context/cli.rb +254 -0
- data/lib/agent/session_context/config.rb +206 -0
- data/lib/agent/session_context/errors.rb +15 -0
- data/lib/agent/session_context/evidence_collector.rb +227 -0
- data/lib/agent/session_context/evidence_packet.rb +271 -0
- data/lib/agent/session_context/immutable_value.rb +71 -0
- data/lib/agent/session_context/injected_context.rb +92 -0
- data/lib/agent/session_context/injected_context_collector.rb +53 -0
- data/lib/agent/session_context/item.rb +54 -0
- data/lib/agent/session_context/loop.rb +122 -0
- data/lib/agent/session_context/loop_view.rb +248 -0
- data/lib/agent/session_context/prompt.rb +40 -0
- data/lib/agent/session_context/prompt_extractor.rb +31 -0
- data/lib/agent/session_context/renderers/human_display.rb +113 -0
- data/lib/agent/session_context/renderers/json.rb +21 -0
- data/lib/agent/session_context/renderers/json_lines.rb +25 -0
- data/lib/agent/session_context/renderers/markdown.rb +130 -0
- data/lib/agent/session_context/renderers/serializer.rb +124 -0
- data/lib/agent/session_context/renderers/text.rb +128 -0
- data/lib/agent/session_context/semantic_categories.rb +89 -0
- data/lib/agent/session_context/semantic_pipeline.rb +151 -0
- data/lib/agent/session_context/semantic_schema.rb +75 -0
- data/lib/agent/session_context/session_resolver.rb +147 -0
- data/lib/agent/session_context/snapshot.rb +128 -0
- data/lib/agent/session_context/source_ref.rb +46 -0
- data/lib/agent/session_context/subprocess_runner.rb +362 -0
- data/lib/agent/session_context/summarizers/claude.rb +126 -0
- data/lib/agent/session_context/summarizers/codex.rb +132 -0
- data/lib/agent/session_context/summarizers/command_execution_policy.rb +134 -0
- data/lib/agent/session_context/summarizers.rb +35 -0
- data/lib/agent/session_context/summary_parser.rb +219 -0
- data/lib/agent/session_context/tool_call.rb +21 -0
- data/lib/agent/session_context/transcript.rb +236 -0
- data/lib/agent/session_context/version.rb +7 -0
- data/lib/agent/session_context.rb +60 -0
- metadata +115 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agent
|
|
4
|
+
module SessionContext
|
|
5
|
+
TranscriptPart = Data.define(:index, :type, :text, :name, :call_id, :injected, :source_ref) do
|
|
6
|
+
def initialize(index:, type:, injected:, source_ref:, text: nil, name: nil, call_id: nil)
|
|
7
|
+
super(
|
|
8
|
+
index: normalize_positive_index(index, :index),
|
|
9
|
+
type: normalize_symbol(type, :type),
|
|
10
|
+
text: normalize_optional_string(text, :text),
|
|
11
|
+
name: normalize_optional_string(name, :name),
|
|
12
|
+
call_id: normalize_optional_string(call_id, :call_id),
|
|
13
|
+
injected: normalize_boolean(injected),
|
|
14
|
+
source_ref: normalize_source_ref(source_ref)
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def normalize_positive_index(value, name)
|
|
21
|
+
index =
|
|
22
|
+
if value.is_a?(Integer)
|
|
23
|
+
value
|
|
24
|
+
elsif value.respond_to?(:to_int)
|
|
25
|
+
value.to_int
|
|
26
|
+
elsif value.is_a?(String)
|
|
27
|
+
Integer(value, exception: false)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
raise TypeError, "#{name} must be an Integer or integer-like value" if index.nil?
|
|
31
|
+
raise ArgumentError, "#{name} must be greater than or equal to 1" if index < 1
|
|
32
|
+
|
|
33
|
+
index
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def normalize_symbol(value, name)
|
|
37
|
+
return value if value.is_a?(Symbol)
|
|
38
|
+
return value.to_sym if value.respond_to?(:to_sym)
|
|
39
|
+
|
|
40
|
+
raise TypeError, "#{name} must be symbolizable"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def normalize_optional_string(value, name)
|
|
44
|
+
return if value.nil?
|
|
45
|
+
|
|
46
|
+
raise TypeError, "#{name} must be a String" unless value.respond_to?(:to_str)
|
|
47
|
+
|
|
48
|
+
String.new(value.to_str).freeze
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def normalize_boolean(value)
|
|
52
|
+
return value if [true, false].include?(value)
|
|
53
|
+
|
|
54
|
+
raise TypeError, "injected must be true or false"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def normalize_source_ref(value)
|
|
58
|
+
raise TypeError, "source_ref must be an Agent::SessionContext::SourceRef" unless value.is_a?(SourceRef)
|
|
59
|
+
|
|
60
|
+
value
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
TranscriptEntry = Data.define(:index, :role, :at, :parts) do
|
|
65
|
+
def initialize(index:, role:, at:, parts:)
|
|
66
|
+
super(
|
|
67
|
+
index: normalize_positive_index(index, :index),
|
|
68
|
+
role: normalize_symbol(role, :role),
|
|
69
|
+
at: at,
|
|
70
|
+
parts: normalize_parts(parts)
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def normalize_positive_index(value, name)
|
|
77
|
+
index =
|
|
78
|
+
if value.is_a?(Integer)
|
|
79
|
+
value
|
|
80
|
+
elsif value.respond_to?(:to_int)
|
|
81
|
+
value.to_int
|
|
82
|
+
elsif value.is_a?(String)
|
|
83
|
+
Integer(value, exception: false)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
raise TypeError, "#{name} must be an Integer or integer-like value" if index.nil?
|
|
87
|
+
raise ArgumentError, "#{name} must be greater than or equal to 1" if index < 1
|
|
88
|
+
|
|
89
|
+
index
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def normalize_symbol(value, name)
|
|
93
|
+
return value if value.is_a?(Symbol)
|
|
94
|
+
return value.to_sym if value.respond_to?(:to_sym)
|
|
95
|
+
|
|
96
|
+
raise TypeError, "#{name} must be symbolizable"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def normalize_parts(value)
|
|
100
|
+
Array(value).map do |part|
|
|
101
|
+
unless part.is_a?(TranscriptPart)
|
|
102
|
+
raise TypeError,
|
|
103
|
+
"parts must contain only Agent::SessionContext::TranscriptPart values"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
part
|
|
107
|
+
end.freeze
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
Transcript = Data.define(:session, :captured_at, :entries, :warnings) do
|
|
112
|
+
class << self
|
|
113
|
+
def capture(session, reader: Agent::Sessions.read(session), now: Time.now)
|
|
114
|
+
entries = []
|
|
115
|
+
|
|
116
|
+
reader.each_message.with_index(1) do |message, message_index|
|
|
117
|
+
entries << TranscriptEntry.new(
|
|
118
|
+
index: message_index,
|
|
119
|
+
role: message.role,
|
|
120
|
+
at: message.at,
|
|
121
|
+
parts: capture_parts(session, message, message_index)
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
new(
|
|
126
|
+
session: session,
|
|
127
|
+
captured_at: now,
|
|
128
|
+
entries: entries.freeze,
|
|
129
|
+
warnings: capture_warnings(reader)
|
|
130
|
+
)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def injection_kind(agent, text)
|
|
134
|
+
return unless text.is_a?(String)
|
|
135
|
+
|
|
136
|
+
stripped_text = text.b.lstrip
|
|
137
|
+
marker_kinds = Transcript::INJECTION_MARKERS.fetch(agent&.to_sym, {})
|
|
138
|
+
match = marker_kinds.find { |marker, _kind| stripped_text.start_with?(marker.b) }
|
|
139
|
+
match&.last
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
def capture_parts(session, message, message_index)
|
|
145
|
+
message.parts.each_with_index.map do |part, part_index|
|
|
146
|
+
TranscriptPart.new(
|
|
147
|
+
index: part_index + 1,
|
|
148
|
+
type: part.type,
|
|
149
|
+
text: part.text,
|
|
150
|
+
name: part.name,
|
|
151
|
+
call_id: part.call_id,
|
|
152
|
+
injected: injected_part?(session.agent, message, part),
|
|
153
|
+
source_ref: SourceRef.new(
|
|
154
|
+
session_uid: session.uid,
|
|
155
|
+
message_index: message_index,
|
|
156
|
+
part_index: part_index + 1
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
end.freeze
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def capture_warnings(reader)
|
|
163
|
+
Array(reader.warnings).map do |warning|
|
|
164
|
+
raise TypeError, "warning must be a String" unless warning.respond_to?(:to_str)
|
|
165
|
+
|
|
166
|
+
String.new(warning.to_str).freeze
|
|
167
|
+
end.freeze
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def injected_part?(agent, message, part)
|
|
171
|
+
return false unless message.role == :user
|
|
172
|
+
return false unless part.type == :text
|
|
173
|
+
|
|
174
|
+
raw_meta_message?(message.raw) || marker_injected?(agent, part.text)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def raw_meta_message?(raw)
|
|
178
|
+
raw.is_a?(Hash) && raw["isMeta"] == true
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def marker_injected?(agent, text)
|
|
182
|
+
!injection_kind(agent, text).nil?
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def initialize(session:, captured_at:, entries:, warnings:)
|
|
187
|
+
super(
|
|
188
|
+
session: session,
|
|
189
|
+
captured_at: captured_at,
|
|
190
|
+
entries: normalize_entries(entries),
|
|
191
|
+
warnings: normalize_warnings(warnings)
|
|
192
|
+
)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
private
|
|
196
|
+
|
|
197
|
+
def normalize_entries(value)
|
|
198
|
+
Array(value).map do |entry|
|
|
199
|
+
unless entry.is_a?(TranscriptEntry)
|
|
200
|
+
raise TypeError,
|
|
201
|
+
"entries must contain only Agent::SessionContext::TranscriptEntry values"
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
entry
|
|
205
|
+
end.freeze
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def normalize_warnings(value)
|
|
209
|
+
Array(value).map do |warning|
|
|
210
|
+
raise TypeError, "warning must be a String" unless warning.respond_to?(:to_str)
|
|
211
|
+
|
|
212
|
+
String.new(warning.to_str).freeze
|
|
213
|
+
end.freeze
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
Transcript.const_set(
|
|
218
|
+
:INJECTION_MARKERS,
|
|
219
|
+
{
|
|
220
|
+
claude: {
|
|
221
|
+
"<command-name>" => :command_name,
|
|
222
|
+
"<command-message>" => :command_message,
|
|
223
|
+
"<command-args>" => :command_args,
|
|
224
|
+
"<local-command-stdout>" => :local_command_stdout,
|
|
225
|
+
"<local-command-stderr>" => :local_command_stderr,
|
|
226
|
+
"<system-reminder>" => :system_reminder
|
|
227
|
+
}.freeze,
|
|
228
|
+
codex: {
|
|
229
|
+
"<environment_context>" => :environment_context,
|
|
230
|
+
"<user_instructions>" => :user_instructions,
|
|
231
|
+
"# AGENTS.md instructions" => :agents_instructions
|
|
232
|
+
}.freeze
|
|
233
|
+
}.freeze
|
|
234
|
+
)
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "time"
|
|
5
|
+
require "agent_sessions"
|
|
6
|
+
require "zeitwerk"
|
|
7
|
+
require_relative "session_context/errors"
|
|
8
|
+
|
|
9
|
+
module Agent
|
|
10
|
+
module SessionContext
|
|
11
|
+
LOADER = Zeitwerk::Loader.for_gem_extension(Agent)
|
|
12
|
+
LOADER.ignore(File.expand_path("session_context/errors.rb", __dir__))
|
|
13
|
+
LOADER.inflector.inflect("cli" => "CLI", "json" => "JSON", "json_lines" => "JSONLines")
|
|
14
|
+
LOADER.setup
|
|
15
|
+
private_constant :LOADER
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
def resolve(identifier, agent: nil, env: ENV, catalog: Agent::Sessions)
|
|
19
|
+
SessionResolver.new(catalog:, env: env).resolve(identifier, agent: agent)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def current(agent: nil, env: ENV, catalog: Agent::Sessions)
|
|
23
|
+
SessionResolver.new(catalog:, env: env).current(agent:)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def show(session, include_injected: false, **)
|
|
27
|
+
Builder.new(**).show(session, include_injected:)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def prompts(session, **)
|
|
31
|
+
Builder.new(**).prompts(session)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def loop(session, **)
|
|
35
|
+
Builder.new(**).loop(session)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def summarize(
|
|
39
|
+
session,
|
|
40
|
+
using: nil,
|
|
41
|
+
summarizer: nil,
|
|
42
|
+
timeout: nil,
|
|
43
|
+
env: ENV,
|
|
44
|
+
config_loader: Config,
|
|
45
|
+
backend_factory: Summarizers,
|
|
46
|
+
**
|
|
47
|
+
)
|
|
48
|
+
if summarizer
|
|
49
|
+
raise ArgumentError, "timeout applies only to built-in summarizers" unless timeout.nil?
|
|
50
|
+
|
|
51
|
+
return Builder.new(**).summarize(session, summarizer:)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
config = config_loader.load(session:, env:, timeout:)
|
|
55
|
+
backend = backend_factory.for(using || session.agent, timeout_seconds: config.timeout_seconds)
|
|
56
|
+
Builder.new(**).summarize(session, summarizer: backend)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: agent_session_context
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Lucian Ghinda
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: agent_sessions
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.4'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.4'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: zeitwerk
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.8'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.8'
|
|
40
|
+
description: Shows exact prompts and evidence-backed context summaries for Claude
|
|
41
|
+
Code and Codex CLI sessions.
|
|
42
|
+
email:
|
|
43
|
+
- dev@ghinda.com
|
|
44
|
+
executables:
|
|
45
|
+
- agent-session-context
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- CHANGELOG.md
|
|
50
|
+
- LICENSE.txt
|
|
51
|
+
- README.md
|
|
52
|
+
- exe/agent-session-context
|
|
53
|
+
- lib/agent/session_context.rb
|
|
54
|
+
- lib/agent/session_context/builder.rb
|
|
55
|
+
- lib/agent/session_context/cli.rb
|
|
56
|
+
- lib/agent/session_context/cli/options.rb
|
|
57
|
+
- lib/agent/session_context/config.rb
|
|
58
|
+
- lib/agent/session_context/errors.rb
|
|
59
|
+
- lib/agent/session_context/evidence_collector.rb
|
|
60
|
+
- lib/agent/session_context/evidence_packet.rb
|
|
61
|
+
- lib/agent/session_context/immutable_value.rb
|
|
62
|
+
- lib/agent/session_context/injected_context.rb
|
|
63
|
+
- lib/agent/session_context/injected_context_collector.rb
|
|
64
|
+
- lib/agent/session_context/item.rb
|
|
65
|
+
- lib/agent/session_context/loop.rb
|
|
66
|
+
- lib/agent/session_context/loop_view.rb
|
|
67
|
+
- lib/agent/session_context/prompt.rb
|
|
68
|
+
- lib/agent/session_context/prompt_extractor.rb
|
|
69
|
+
- lib/agent/session_context/renderers/human_display.rb
|
|
70
|
+
- lib/agent/session_context/renderers/json.rb
|
|
71
|
+
- lib/agent/session_context/renderers/json_lines.rb
|
|
72
|
+
- lib/agent/session_context/renderers/markdown.rb
|
|
73
|
+
- lib/agent/session_context/renderers/serializer.rb
|
|
74
|
+
- lib/agent/session_context/renderers/text.rb
|
|
75
|
+
- lib/agent/session_context/semantic_categories.rb
|
|
76
|
+
- lib/agent/session_context/semantic_pipeline.rb
|
|
77
|
+
- lib/agent/session_context/semantic_schema.rb
|
|
78
|
+
- lib/agent/session_context/session_resolver.rb
|
|
79
|
+
- lib/agent/session_context/snapshot.rb
|
|
80
|
+
- lib/agent/session_context/source_ref.rb
|
|
81
|
+
- lib/agent/session_context/subprocess_runner.rb
|
|
82
|
+
- lib/agent/session_context/summarizers.rb
|
|
83
|
+
- lib/agent/session_context/summarizers/claude.rb
|
|
84
|
+
- lib/agent/session_context/summarizers/codex.rb
|
|
85
|
+
- lib/agent/session_context/summarizers/command_execution_policy.rb
|
|
86
|
+
- lib/agent/session_context/summary_parser.rb
|
|
87
|
+
- lib/agent/session_context/tool_call.rb
|
|
88
|
+
- lib/agent/session_context/transcript.rb
|
|
89
|
+
- lib/agent/session_context/version.rb
|
|
90
|
+
homepage: https://github.com/lucianghinda/agent_session_context
|
|
91
|
+
licenses:
|
|
92
|
+
- MIT
|
|
93
|
+
metadata:
|
|
94
|
+
bug_tracker_uri: https://github.com/lucianghinda/agent_session_context/issues
|
|
95
|
+
changelog_uri: https://github.com/lucianghinda/agent_session_context/blob/main/CHANGELOG.md
|
|
96
|
+
rubygems_mfa_required: 'true'
|
|
97
|
+
source_code_uri: https://github.com/lucianghinda/agent_session_context
|
|
98
|
+
rdoc_options: []
|
|
99
|
+
require_paths:
|
|
100
|
+
- lib
|
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
102
|
+
requirements:
|
|
103
|
+
- - ">="
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: 3.2.0
|
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
requirements: []
|
|
112
|
+
rubygems_version: 4.0.11
|
|
113
|
+
specification_version: 4
|
|
114
|
+
summary: Inspect and summarize recorded AI agent session context
|
|
115
|
+
test_files: []
|