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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +43 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +298 -0
  5. data/exe/agent-session-context +6 -0
  6. data/lib/agent/session_context/builder.rb +150 -0
  7. data/lib/agent/session_context/cli/options.rb +214 -0
  8. data/lib/agent/session_context/cli.rb +254 -0
  9. data/lib/agent/session_context/config.rb +206 -0
  10. data/lib/agent/session_context/errors.rb +15 -0
  11. data/lib/agent/session_context/evidence_collector.rb +227 -0
  12. data/lib/agent/session_context/evidence_packet.rb +271 -0
  13. data/lib/agent/session_context/immutable_value.rb +71 -0
  14. data/lib/agent/session_context/injected_context.rb +92 -0
  15. data/lib/agent/session_context/injected_context_collector.rb +53 -0
  16. data/lib/agent/session_context/item.rb +54 -0
  17. data/lib/agent/session_context/loop.rb +122 -0
  18. data/lib/agent/session_context/loop_view.rb +248 -0
  19. data/lib/agent/session_context/prompt.rb +40 -0
  20. data/lib/agent/session_context/prompt_extractor.rb +31 -0
  21. data/lib/agent/session_context/renderers/human_display.rb +113 -0
  22. data/lib/agent/session_context/renderers/json.rb +21 -0
  23. data/lib/agent/session_context/renderers/json_lines.rb +25 -0
  24. data/lib/agent/session_context/renderers/markdown.rb +130 -0
  25. data/lib/agent/session_context/renderers/serializer.rb +124 -0
  26. data/lib/agent/session_context/renderers/text.rb +128 -0
  27. data/lib/agent/session_context/semantic_categories.rb +89 -0
  28. data/lib/agent/session_context/semantic_pipeline.rb +151 -0
  29. data/lib/agent/session_context/semantic_schema.rb +75 -0
  30. data/lib/agent/session_context/session_resolver.rb +147 -0
  31. data/lib/agent/session_context/snapshot.rb +128 -0
  32. data/lib/agent/session_context/source_ref.rb +46 -0
  33. data/lib/agent/session_context/subprocess_runner.rb +362 -0
  34. data/lib/agent/session_context/summarizers/claude.rb +126 -0
  35. data/lib/agent/session_context/summarizers/codex.rb +132 -0
  36. data/lib/agent/session_context/summarizers/command_execution_policy.rb +134 -0
  37. data/lib/agent/session_context/summarizers.rb +35 -0
  38. data/lib/agent/session_context/summary_parser.rb +219 -0
  39. data/lib/agent/session_context/tool_call.rb +21 -0
  40. data/lib/agent/session_context/transcript.rb +236 -0
  41. data/lib/agent/session_context/version.rb +7 -0
  42. data/lib/agent/session_context.rb +60 -0
  43. 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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Agent
4
+ module SessionContext
5
+ VERSION = "1.0.0"
6
+ end
7
+ 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: []