lex-microsoft_teams 0.6.16 → 0.6.17

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ad20d81a8d2c2a597d2415f896f8ae4c8209efde74efd99fd0c1b630cc2ce60
4
- data.tar.gz: 44d7b8e2d0831df8116a44379096f9264d6d27302d8787fdc97a9652762f5c3b
3
+ metadata.gz: 832e104a03125c669c37edf62529da10ffb5bd5b4267de12ba91c764a4e168ae
4
+ data.tar.gz: f6c47a1c6af1903e9bec4aec82363f4d01a439d353236144213f3c677923c14f
5
5
  SHA512:
6
- metadata.gz: e6d66af6d00faed231e1d889e60f906f22f03a79ff9ebced10745fd41cb0a260929f1fb25222e39a6a6c0705414799e768a36ab1f163cdf7c67980654c3ccbb7
7
- data.tar.gz: 54fe1f02a29d2edb885bec85f56c94173b7857815d01532299fb05e985535eace1237a9322bc54a0752efdb68213e2e7701d27fecbd765f6bb250b4ae4361216
6
+ metadata.gz: 16bac537f53a87e92a2d37ee697eb8759739e67530e4262c18378f7af532afe5c192c3843d4bfdaafc807636b9fafea83381d36089bbaa21888ed5decf734907
7
+ data.tar.gz: daf6dda8b96102a644d002b7890f2faf3b343f9600bd9c8e00fa76a5db1b0ad380ab9b37addb7480a12b71f0bbbd1ac9bc00247d6d1b75dd451b19953062f93b
data/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@
5
5
  ### Changed
6
6
  - add caller identity to llm_chat calls in bot and profile_ingest runners for pipeline attribution
7
7
 
8
+ ## [0.6.17] - 2026-03-24
9
+
10
+ ### Added
11
+ - `Helpers::TraceRetriever` module: retrieves memory traces from the shared store at query time and formats them as LLM context (sender, teams, and chat-scoped domains; 2000-token budget; strength-ranked deduplication)
12
+ - `Bot#retrieve_trace_context` private method wires TraceRetriever into the handle_message flow
13
+ - `Bot#handle_message` now retrieves trace context before generating a response and passes it through to `generate_response` and `llm_respond`
14
+ - `SessionManager#get_or_create` seeds new sessions with profile traces for the owner via `trace_seed_for`
15
+ - `PromptResolver#resolve_prompt` accepts optional `trace_context:` keyword and appends it after preference instructions
16
+ - Comprehensive specs for TraceRetriever (token budget, rank/dedup, age labels, graceful degradation)
17
+ - Bot specs updated to verify trace context retrieval and pass-through, and nil/graceful-degradation paths
18
+
8
19
  ## [0.6.15] - 2026-03-23
9
20
 
10
21
  ### Added
@@ -5,7 +5,7 @@ module Legion
5
5
  module MicrosoftTeams
6
6
  module Helpers
7
7
  module PromptResolver
8
- def resolve_prompt(mode:, conversation_id:, owner_id: nil)
8
+ def resolve_prompt(mode:, conversation_id:, owner_id: nil, trace_context: nil)
9
9
  settings = teams_settings
10
10
  base = settings.dig(:bot, :system_prompt) || ''
11
11
 
@@ -18,6 +18,8 @@ module Legion
18
18
  pref_instructions = preference_instructions_for(owner_id: owner_id)
19
19
  prompt = "#{prompt}\n\n#{pref_instructions}" if pref_instructions
20
20
 
21
+ prompt = "#{prompt}\n\n#{trace_context}" if trace_context && !trace_context.empty?
22
+
21
23
  prompt
22
24
  end
23
25
 
@@ -35,6 +35,9 @@ module Legion
35
35
  llm_config: resolve_llm_config(mode: mode, conversation_id: conversation_id, owner_id: owner_id)
36
36
  }
37
37
 
38
+ seed = trace_seed_for(owner_id: owner_id)
39
+ session[:trace_seed] = seed if seed
40
+
38
41
  @sessions[conversation_id] = session
39
42
  end
40
43
 
@@ -134,6 +137,22 @@ module Legion
134
137
  @memory_runner ||= Object.new.extend(Legion::Extensions::Agentic::Memory::Trace::Runners::Traces)
135
138
  end
136
139
 
140
+ def trace_seed_for(owner_id:)
141
+ return nil unless defined?(Legion::Extensions::Agentic::Memory::Trace) &&
142
+ Legion::Extensions::Agentic::Memory::Trace.respond_to?(:shared_store)
143
+
144
+ store = Legion::Extensions::Agentic::Memory::Trace.shared_store
145
+ return nil unless store
146
+
147
+ profile_traces = store.retrieve_by_domain("sender:#{owner_id}", min_strength: 0.3, limit: 5)
148
+ return nil if profile_traces.empty?
149
+
150
+ profile_traces.map { |t| { type: t[:trace_type], content: t[:content_payload].to_s[0, 200] } }
151
+ rescue StandardError => e
152
+ log.debug("trace_seed_for failed: #{e.message}") if defined?(log) && log.respond_to?(:debug)
153
+ nil
154
+ end
155
+
137
156
  def settings_val(key, default)
138
157
  if defined?(Legion::Settings) && Legion::Settings.dig(:microsoft_teams, :bot, :session, key)
139
158
  Legion::Settings[:microsoft_teams][:bot][:session][key]
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module MicrosoftTeams
6
+ module Helpers
7
+ module TraceRetriever
8
+ MAX_TRACE_TOKENS = 2000
9
+ MAX_TRACES = 20
10
+
11
+ def retrieve_context(message:, owner_id:, chat_id: nil, channel_id: nil) # rubocop:disable Lint/UnusedMethodArgument
12
+ return nil unless memory_trace_available?
13
+
14
+ traces = []
15
+ traces.concat(retrieve_sender_traces(owner_id: owner_id))
16
+ traces.concat(retrieve_teams_traces)
17
+ traces.concat(retrieve_chat_traces(chat_id: chat_id)) if chat_id
18
+
19
+ ranked = rank_traces(traces: traces, query: message)
20
+ format_trace_context(traces: ranked.first(MAX_TRACES))
21
+ rescue StandardError => e
22
+ log_trace_error('retrieve_context', e)
23
+ nil
24
+ end
25
+
26
+ private
27
+
28
+ def retrieve_sender_traces(owner_id:)
29
+ return [] unless owner_id
30
+
31
+ store = shared_trace_store
32
+ return [] unless store
33
+
34
+ store.retrieve_by_domain("sender:#{owner_id}", min_strength: 0.1, limit: 10)
35
+ rescue StandardError => e
36
+ log_trace_error('retrieve_sender_traces', e)
37
+ []
38
+ end
39
+
40
+ def retrieve_teams_traces
41
+ store = shared_trace_store
42
+ return [] unless store
43
+
44
+ store.retrieve_by_domain('teams', min_strength: 0.3, limit: 10)
45
+ rescue StandardError => e
46
+ log_trace_error('retrieve_teams_traces', e)
47
+ []
48
+ end
49
+
50
+ def retrieve_chat_traces(chat_id:)
51
+ store = shared_trace_store
52
+ return [] unless store
53
+
54
+ store.retrieve_by_domain("chat:#{chat_id}", min_strength: 0.1, limit: 5)
55
+ rescue StandardError => e
56
+ log_trace_error('retrieve_chat_traces', e)
57
+ []
58
+ end
59
+
60
+ def rank_traces(traces:, query:) # rubocop:disable Lint/UnusedMethodArgument
61
+ seen = Set.new
62
+ unique = traces.select { |t| seen.add?(t[:trace_id]) }
63
+
64
+ unique.sort_by { |t| [-(t[:strength] || 0.0), -t[:last_reinforced].to_f] }
65
+ end
66
+
67
+ def format_trace_context(traces:)
68
+ return nil if traces.empty?
69
+
70
+ lines = ['## Organizational Context (from memory)']
71
+ token_estimate = 0
72
+
73
+ traces.each do |trace|
74
+ line = format_single_trace(trace)
75
+ next unless line
76
+
77
+ line_tokens = line.length / 4
78
+ break if token_estimate + line_tokens > MAX_TRACE_TOKENS
79
+
80
+ lines << line
81
+ token_estimate += line_tokens
82
+ end
83
+
84
+ return nil if lines.size <= 1
85
+
86
+ lines.join("\n")
87
+ end
88
+
89
+ def format_single_trace(trace)
90
+ type = trace[:trace_type] || :unknown
91
+ content = trace[:content_payload].to_s
92
+ content = "#{content[0, 200]}..." if content.length > 200
93
+
94
+ tags = (trace[:domain_tags] || []).join(', ')
95
+ age = trace_age_label(trace[:created_at] || trace[:last_reinforced])
96
+
97
+ "- [#{type}] #{content} (#{age}, tags: #{tags})"
98
+ rescue StandardError
99
+ nil
100
+ end
101
+
102
+ def trace_age_label(timestamp)
103
+ return 'unknown age' unless timestamp
104
+
105
+ seconds = Time.now - (timestamp.is_a?(Time) ? timestamp : Time.parse(timestamp.to_s))
106
+ case seconds
107
+ when 0..3600 then 'just now'
108
+ when 3600..86_400 then "#{(seconds / 3600).to_i}h ago"
109
+ when 86_400..604_800 then "#{(seconds / 86_400).to_i}d ago"
110
+ else "#{(seconds / 604_800).to_i}w ago"
111
+ end
112
+ rescue StandardError
113
+ 'unknown age'
114
+ end
115
+
116
+ def memory_trace_available?
117
+ defined?(Legion::Extensions::Agentic::Memory::Trace)
118
+ end
119
+
120
+ def shared_trace_store
121
+ return nil unless defined?(Legion::Extensions::Agentic::Memory::Trace) &&
122
+ Legion::Extensions::Agentic::Memory::Trace.respond_to?(:shared_store)
123
+
124
+ Legion::Extensions::Agentic::Memory::Trace.shared_store
125
+ end
126
+
127
+ def log_trace_error(method, error)
128
+ return unless defined?(log)
129
+
130
+ log.debug("TraceRetriever##{method} failed: #{error.message}")
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -4,6 +4,7 @@ require 'json'
4
4
  require 'legion/extensions/microsoft_teams/helpers/client'
5
5
  require_relative '../helpers/session_manager'
6
6
  require_relative '../helpers/subscription_registry'
7
+ require_relative '../helpers/trace_retriever'
7
8
 
8
9
  module Legion
9
10
  module Extensions
@@ -12,6 +13,7 @@ module Legion
12
13
  module Bot
13
14
  include Legion::Extensions::MicrosoftTeams::Helpers::Client
14
15
  include Legion::Extensions::MicrosoftTeams::Helpers::PromptResolver
16
+ include Legion::Extensions::MicrosoftTeams::Helpers::TraceRetriever
15
17
 
16
18
  def send_activity(service_url:, conversation_id:, activity:, **)
17
19
  conn = bot_connection(service_url: service_url, **)
@@ -97,7 +99,8 @@ module Legion
97
99
  )
98
100
  session_manager.add_message(conversation_id: conversation_id, role: :user, content: text)
99
101
 
100
- response_text = generate_response(text: text, session: session)
102
+ trace_context = retrieve_trace_context(message: text, owner_id: owner_id, chat_id: chat_id)
103
+ response_text = generate_response(text: text, session: session, trace_context: trace_context)
101
104
 
102
105
  reply_result = send_reply(
103
106
  chat_id: chat_id,
@@ -166,17 +169,20 @@ module Legion
166
169
 
167
170
  private
168
171
 
169
- def generate_response(text:, session:)
170
- return llm_respond(text: text, session: session) if llm_available?
172
+ def generate_response(text:, session:, trace_context: nil)
173
+ return llm_respond(text: text, session: session, trace_context: trace_context) if llm_available?
171
174
 
172
175
  "Echo: #{text}"
173
176
  end
174
177
 
175
- def llm_respond(text:, session:)
178
+ def llm_respond(text:, session:, trace_context: nil)
176
179
  config = session[:llm_config] || {}
180
+ instructions = session[:system_prompt]
181
+ instructions = "#{instructions}\n\n#{trace_context}" if trace_context && !trace_context.empty?
182
+
177
183
  response = llm_chat(
178
184
  text,
179
- instructions: session[:system_prompt],
185
+ instructions: instructions,
180
186
  model: config[:model],
181
187
  intent: config[:intent],
182
188
  caller: { id: session[:owner_id], extension: 'lex-microsoft_teams', mode: :bot_response }
@@ -187,6 +193,15 @@ module Legion
187
193
  'I encountered an error processing your message. Please try again.'
188
194
  end
189
195
 
196
+ def retrieve_trace_context(message:, owner_id:, chat_id:)
197
+ return nil unless respond_to?(:retrieve_context, true)
198
+
199
+ retrieve_context(message: message, owner_id: owner_id, chat_id: chat_id)
200
+ rescue StandardError => e
201
+ log.debug("retrieve_trace_context failed: #{e.message}") if defined?(log)
202
+ nil
203
+ end
204
+
190
205
  def llm_available?
191
206
  defined?(Legion::LLM) && Legion::LLM.respond_to?(:chat)
192
207
  end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module MicrosoftTeams
6
- VERSION = '0.6.16'
6
+ VERSION = '0.6.17'
7
7
  end
8
8
  end
9
9
  end
@@ -25,6 +25,7 @@ require 'legion/extensions/microsoft_teams/runners/api_ingest'
25
25
  # Helpers (bot)
26
26
  require 'legion/extensions/microsoft_teams/helpers/high_water_mark'
27
27
  require 'legion/extensions/microsoft_teams/helpers/prompt_resolver'
28
+ require 'legion/extensions/microsoft_teams/helpers/trace_retriever'
28
29
  require 'legion/extensions/microsoft_teams/helpers/session_manager'
29
30
  require 'legion/extensions/microsoft_teams/helpers/subscription_registry'
30
31
  require 'legion/extensions/microsoft_teams/helpers/token_cache'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-microsoft_teams
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.16
4
+ version: 0.6.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -197,6 +197,7 @@ files:
197
197
  - lib/legion/extensions/microsoft_teams/helpers/session_manager.rb
198
198
  - lib/legion/extensions/microsoft_teams/helpers/subscription_registry.rb
199
199
  - lib/legion/extensions/microsoft_teams/helpers/token_cache.rb
200
+ - lib/legion/extensions/microsoft_teams/helpers/trace_retriever.rb
200
201
  - lib/legion/extensions/microsoft_teams/helpers/transform_definitions.rb
201
202
  - lib/legion/extensions/microsoft_teams/hooks/auth.rb
202
203
  - lib/legion/extensions/microsoft_teams/local_cache/extractor.rb