lex-microsoft_teams 0.6.45 → 0.6.47
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 +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +2 -0
- data/CLAUDE.md +29 -266
- data/lex-microsoft_teams.gemspec +1 -0
- data/lib/legion/extensions/microsoft_teams/absorbers/channel.rb +29 -17
- data/lib/legion/extensions/microsoft_teams/absorbers/chat.rb +20 -14
- data/lib/legion/extensions/microsoft_teams/absorbers/meeting.rb +21 -14
- data/lib/legion/extensions/microsoft_teams/actors/absorb_channel.rb +7 -4
- data/lib/legion/extensions/microsoft_teams/actors/absorb_chat.rb +7 -4
- data/lib/legion/extensions/microsoft_teams/actors/absorb_meeting.rb +7 -4
- data/lib/legion/extensions/microsoft_teams/actors/api_ingest.rb +13 -15
- data/lib/legion/extensions/microsoft_teams/actors/cache_bulk_ingest.rb +3 -3
- data/lib/legion/extensions/microsoft_teams/actors/cache_sync.rb +2 -1
- data/lib/legion/extensions/microsoft_teams/actors/channel_poller.rb +25 -16
- data/lib/legion/extensions/microsoft_teams/actors/direct_chat_poller.rb +16 -10
- data/lib/legion/extensions/microsoft_teams/actors/incremental_sync.rb +8 -8
- data/lib/legion/extensions/microsoft_teams/actors/meeting_ingest.rb +30 -22
- data/lib/legion/extensions/microsoft_teams/actors/observed_chat_poller.rb +14 -8
- data/lib/legion/extensions/microsoft_teams/actors/presence_poller.rb +14 -8
- data/lib/legion/extensions/microsoft_teams/actors/profile_ingest.rb +13 -16
- data/lib/legion/extensions/microsoft_teams/helpers/client.rb +10 -4
- data/lib/legion/extensions/microsoft_teams/helpers/high_water_mark.rb +3 -2
- data/lib/legion/extensions/microsoft_teams/helpers/prompt_resolver.rb +4 -1
- data/lib/legion/extensions/microsoft_teams/helpers/session_manager.rb +8 -2
- data/lib/legion/extensions/microsoft_teams/helpers/subscription_registry.rb +5 -3
- data/lib/legion/extensions/microsoft_teams/helpers/trace_retriever.rb +6 -1
- data/lib/legion/extensions/microsoft_teams/local_cache/extractor.rb +1 -1
- data/lib/legion/extensions/microsoft_teams/local_cache/sstable_reader.rb +2 -1
- data/lib/legion/extensions/microsoft_teams/runners/activities.rb +43 -0
- data/lib/legion/extensions/microsoft_teams/runners/ai_insights.rb +62 -0
- data/lib/legion/extensions/microsoft_teams/runners/api_ingest.rb +20 -14
- data/lib/legion/extensions/microsoft_teams/runners/app_installations.rb +86 -0
- data/lib/legion/extensions/microsoft_teams/runners/auth.rb +4 -107
- data/lib/legion/extensions/microsoft_teams/runners/bot.rb +20 -12
- data/lib/legion/extensions/microsoft_teams/runners/cache_ingest.rb +9 -5
- data/lib/legion/extensions/microsoft_teams/runners/call_events.rb +72 -0
- data/lib/legion/extensions/microsoft_teams/runners/channel_messages.rb +85 -0
- data/lib/legion/extensions/microsoft_teams/runners/channels.rb +69 -0
- data/lib/legion/extensions/microsoft_teams/runners/chats.rb +57 -0
- data/lib/legion/extensions/microsoft_teams/runners/files.rb +77 -0
- data/lib/legion/extensions/microsoft_teams/runners/local_cache.rb +4 -0
- data/lib/legion/extensions/microsoft_teams/runners/loop.rb +6 -0
- data/lib/legion/extensions/microsoft_teams/runners/meeting_artifacts.rb +54 -0
- data/lib/legion/extensions/microsoft_teams/runners/meetings.rb +92 -0
- data/lib/legion/extensions/microsoft_teams/runners/messages.rb +62 -0
- data/lib/legion/extensions/microsoft_teams/runners/ownership.rb +11 -0
- data/lib/legion/extensions/microsoft_teams/runners/people.rb +25 -0
- data/lib/legion/extensions/microsoft_teams/runners/presence.rb +14 -0
- data/lib/legion/extensions/microsoft_teams/runners/profile_ingest.rb +18 -4
- data/lib/legion/extensions/microsoft_teams/runners/subscriptions.rb +86 -0
- data/lib/legion/extensions/microsoft_teams/runners/teams.rb +30 -0
- data/lib/legion/extensions/microsoft_teams/runners/transcripts.rb +35 -0
- data/lib/legion/extensions/microsoft_teams/version.rb +1 -1
- data/lib/legion/extensions/microsoft_teams.rb +10 -3
- metadata +20 -8
- data/lib/legion/extensions/microsoft_teams/actors/auth_validator.rb +0 -123
- data/lib/legion/extensions/microsoft_teams/actors/token_refresher.rb +0 -122
- data/lib/legion/extensions/microsoft_teams/cli/auth.rb +0 -94
- data/lib/legion/extensions/microsoft_teams/helpers/browser_auth.rb +0 -270
- data/lib/legion/extensions/microsoft_teams/helpers/callback_server.rb +0 -90
- data/lib/legion/extensions/microsoft_teams/helpers/token_cache.rb +0 -412
- data/lib/legion/extensions/microsoft_teams/hooks/auth.rb +0 -17
|
@@ -11,6 +11,7 @@ module Legion
|
|
|
11
11
|
description 'Absorbs Teams meeting transcripts, AI insights, and participants into Apollo'
|
|
12
12
|
|
|
13
13
|
def absorb(url: nil, content: nil, metadata: {}, context: {}) # rubocop:disable Lint/UnusedMethodArgument
|
|
14
|
+
log.debug("Meeting#absorb url=#{url.inspect}")
|
|
14
15
|
report_progress(message: 'resolving meeting from link')
|
|
15
16
|
meeting = resolve_meeting(url)
|
|
16
17
|
return { success: false, error: 'could not resolve meeting' } unless meeting
|
|
@@ -26,9 +27,10 @@ module Legion
|
|
|
26
27
|
ingest_participants(meeting, subject, results)
|
|
27
28
|
|
|
28
29
|
report_progress(message: 'done', percent: 100)
|
|
30
|
+
log.info("Meeting#absorb complete meeting_id=#{meeting_id} subject=#{subject} chunks=#{results[:chunks]}")
|
|
29
31
|
results.merge(success: true)
|
|
30
32
|
rescue StandardError => e
|
|
31
|
-
|
|
33
|
+
handle_exception(e, level: :error, operation: 'Meeting#absorb', url: url)
|
|
32
34
|
{ success: false, error: e.message }
|
|
33
35
|
end
|
|
34
36
|
|
|
@@ -47,17 +49,14 @@ module Legion
|
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
def graph_token
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
rescue StandardError => e
|
|
55
|
-
log.warn("graph_token unavailable: #{e.message}")
|
|
56
|
-
nil
|
|
57
|
-
end
|
|
52
|
+
Legion::Extensions::Identity::Entra::Helpers::TokenManager.load_token(:delegated)
|
|
53
|
+
rescue StandardError => e
|
|
54
|
+
handle_exception(e, level: :debug, operation: 'Meeting#graph_token')
|
|
55
|
+
nil
|
|
58
56
|
end
|
|
59
57
|
|
|
60
58
|
def resolve_meeting(url)
|
|
59
|
+
log.debug("Meeting#resolve_meeting url=#{url.inspect}")
|
|
61
60
|
thread_id = extract_meeting_thread_id(url)
|
|
62
61
|
if thread_id
|
|
63
62
|
report_progress(message: 'resolving meeting from chat thread', percent: 5)
|
|
@@ -67,7 +66,7 @@ module Legion
|
|
|
67
66
|
report_progress(message: 'looking up meeting by join URL', percent: 5)
|
|
68
67
|
resolve_meeting_by_join_url(url)
|
|
69
68
|
rescue StandardError => e
|
|
70
|
-
|
|
69
|
+
handle_exception(e, level: :warn, operation: 'Meeting#resolve_meeting', url: url)
|
|
71
70
|
nil
|
|
72
71
|
end
|
|
73
72
|
|
|
@@ -76,11 +75,12 @@ module Legion
|
|
|
76
75
|
match = uri.path.match(%r{/l/chat/(19:meeting_[^/]+)})
|
|
77
76
|
match&.[](1)
|
|
78
77
|
rescue URI::InvalidURIError => e
|
|
79
|
-
|
|
78
|
+
handle_exception(e, level: :debug, operation: 'Meeting#extract_meeting_thread_id', url: url)
|
|
80
79
|
nil
|
|
81
80
|
end
|
|
82
81
|
|
|
83
82
|
def resolve_meeting_from_chat(thread_id)
|
|
83
|
+
log.debug("Meeting#resolve_meeting_from_chat thread_id=#{thread_id}")
|
|
84
84
|
chats_runner = Object.new.extend(Runners::Chats)
|
|
85
85
|
response = chats_runner.get_chat(chat_id: thread_id, token: graph_token)
|
|
86
86
|
body = response.is_a?(Hash) ? response[:result] : nil
|
|
@@ -95,6 +95,7 @@ module Legion
|
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
def resolve_meeting_by_join_url(url)
|
|
98
|
+
log.debug("Meeting#resolve_meeting_by_join_url url=#{url.inspect}")
|
|
98
99
|
response = meetings_runner.get_meeting_by_join_url(join_url: url, token: graph_token)
|
|
99
100
|
return nil unless response.is_a?(Hash)
|
|
100
101
|
|
|
@@ -108,6 +109,7 @@ module Legion
|
|
|
108
109
|
end
|
|
109
110
|
|
|
110
111
|
def ingest_transcript(meeting_id, subject, results)
|
|
112
|
+
log.debug("Meeting#ingest_transcript meeting_id=#{meeting_id} subject=#{subject}")
|
|
111
113
|
report_progress(message: 'fetching transcripts', percent: 20)
|
|
112
114
|
transcripts_response = transcripts_runner.list_transcripts(meeting_id: meeting_id, token: graph_token)
|
|
113
115
|
transcripts_body = transcripts_response.is_a?(Hash) ? transcripts_response[:result] : nil
|
|
@@ -136,11 +138,13 @@ module Legion
|
|
|
136
138
|
)
|
|
137
139
|
results[:chunks] += 1
|
|
138
140
|
end
|
|
141
|
+
log.info("Meeting#ingest_transcript stored #{results[:chunks]} transcript(s) for meeting_id=#{meeting_id}")
|
|
139
142
|
rescue StandardError => e
|
|
140
|
-
|
|
143
|
+
handle_exception(e, level: :warn, operation: 'Meeting#ingest_transcript', meeting_id: meeting_id)
|
|
141
144
|
end
|
|
142
145
|
|
|
143
146
|
def ingest_ai_insights(meeting_id, subject, results)
|
|
147
|
+
log.debug("Meeting#ingest_ai_insights meeting_id=#{meeting_id}")
|
|
144
148
|
report_progress(message: 'fetching AI insights', percent: 60)
|
|
145
149
|
insights = ai_insights_runner.list_meeting_ai_insights(meeting_id: meeting_id, token: graph_token)
|
|
146
150
|
return unless insights.is_a?(Hash)
|
|
@@ -150,8 +154,9 @@ module Legion
|
|
|
150
154
|
return unless items.is_a?(Array) && items.any?
|
|
151
155
|
|
|
152
156
|
items.each { |item| absorb_insight_item(item, meeting_id, subject, results) }
|
|
157
|
+
log.info("Meeting#ingest_ai_insights processed #{items.length} insight(s) for meeting_id=#{meeting_id}")
|
|
153
158
|
rescue StandardError => e
|
|
154
|
-
|
|
159
|
+
handle_exception(e, level: :warn, operation: 'Meeting#ingest_ai_insights', meeting_id: meeting_id)
|
|
155
160
|
end
|
|
156
161
|
|
|
157
162
|
def absorb_insight_item(item, meeting_id, subject, results)
|
|
@@ -175,6 +180,7 @@ module Legion
|
|
|
175
180
|
end
|
|
176
181
|
|
|
177
182
|
def ingest_participants(meeting, subject, results)
|
|
183
|
+
log.debug("Meeting#ingest_participants subject=#{subject}")
|
|
178
184
|
report_progress(message: 'recording participants', percent: 80)
|
|
179
185
|
participants = meeting.dig('participants', 'attendees') || meeting.dig(:participants, :attendees)
|
|
180
186
|
return unless participants.is_a?(Array) && participants.any?
|
|
@@ -192,8 +198,9 @@ module Legion
|
|
|
192
198
|
metadata: { meeting_id: meeting_id, participant_count: names.length }
|
|
193
199
|
)
|
|
194
200
|
results[:chunks] += 1
|
|
201
|
+
log.info("Meeting#ingest_participants stored #{names.length} participants for meeting_id=#{meeting_id}")
|
|
195
202
|
rescue StandardError => e
|
|
196
|
-
|
|
203
|
+
handle_exception(e, level: :warn, operation: 'Meeting#ingest_participants', subject: subject)
|
|
197
204
|
end
|
|
198
205
|
end
|
|
199
206
|
end
|
|
@@ -14,11 +14,12 @@ module Legion
|
|
|
14
14
|
defined?(Legion::Extensions::Absorbers::Base) &&
|
|
15
15
|
defined?(Legion::Extensions::MicrosoftTeams::Absorbers::Channel)
|
|
16
16
|
rescue StandardError => e
|
|
17
|
-
|
|
17
|
+
handle_exception(e, level: :debug, operation: 'AbsorbChannel#enabled?')
|
|
18
18
|
false
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def work(payload)
|
|
22
|
+
log.debug("AbsorbChannel#work payload=#{payload.inspect[0, 100]}")
|
|
22
23
|
parsed = parse_payload(payload)
|
|
23
24
|
absorber = Absorbers::Channel.new
|
|
24
25
|
result = absorber.absorb(
|
|
@@ -30,7 +31,8 @@ module Legion
|
|
|
30
31
|
if result[:success]
|
|
31
32
|
ack!
|
|
32
33
|
else
|
|
33
|
-
|
|
34
|
+
handle_exception(RuntimeError.new(result.inspect), level: :error,
|
|
35
|
+
operation: 'AbsorbChannel#work')
|
|
34
36
|
reject!(requeue: false)
|
|
35
37
|
end
|
|
36
38
|
else
|
|
@@ -38,19 +40,20 @@ module Legion
|
|
|
38
40
|
end
|
|
39
41
|
result
|
|
40
42
|
rescue StandardError => e
|
|
41
|
-
|
|
43
|
+
handle_exception(e, level: :error, operation: 'AbsorbChannel#work')
|
|
42
44
|
reject!(requeue: false)
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
private
|
|
46
48
|
|
|
47
49
|
def parse_payload(payload)
|
|
50
|
+
log.debug('AbsorbChannel#parse_payload')
|
|
48
51
|
data = payload.is_a?(String) ? json_load(payload) : payload
|
|
49
52
|
return {} unless data.is_a?(Hash)
|
|
50
53
|
|
|
51
54
|
data.transform_keys(&:to_sym)
|
|
52
55
|
rescue StandardError => e
|
|
53
|
-
|
|
56
|
+
handle_exception(e, level: :debug, operation: 'AbsorbChannel#parse_payload')
|
|
54
57
|
{}
|
|
55
58
|
end
|
|
56
59
|
end
|
|
@@ -14,11 +14,12 @@ module Legion
|
|
|
14
14
|
defined?(Legion::Extensions::Absorbers::Base) &&
|
|
15
15
|
defined?(Legion::Extensions::MicrosoftTeams::Absorbers::Chat)
|
|
16
16
|
rescue StandardError => e
|
|
17
|
-
|
|
17
|
+
handle_exception(e, level: :debug, operation: 'AbsorbChat#enabled?')
|
|
18
18
|
false
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def work(payload)
|
|
22
|
+
log.debug("AbsorbChat#work payload=#{payload.inspect[0, 100]}")
|
|
22
23
|
parsed = parse_payload(payload)
|
|
23
24
|
absorber = Absorbers::Chat.new
|
|
24
25
|
result = absorber.absorb(
|
|
@@ -30,7 +31,8 @@ module Legion
|
|
|
30
31
|
if result[:success]
|
|
31
32
|
ack!
|
|
32
33
|
else
|
|
33
|
-
|
|
34
|
+
handle_exception(RuntimeError.new(result.inspect), level: :error,
|
|
35
|
+
operation: 'AbsorbChat#work')
|
|
34
36
|
reject!(requeue: false)
|
|
35
37
|
end
|
|
36
38
|
else
|
|
@@ -38,19 +40,20 @@ module Legion
|
|
|
38
40
|
end
|
|
39
41
|
result
|
|
40
42
|
rescue StandardError => e
|
|
41
|
-
|
|
43
|
+
handle_exception(e, level: :error, operation: 'AbsorbChat#work')
|
|
42
44
|
reject!(requeue: false)
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
private
|
|
46
48
|
|
|
47
49
|
def parse_payload(payload)
|
|
50
|
+
log.debug('AbsorbChat#parse_payload')
|
|
48
51
|
data = payload.is_a?(String) ? json_load(payload) : payload
|
|
49
52
|
return {} unless data.is_a?(Hash)
|
|
50
53
|
|
|
51
54
|
data.transform_keys(&:to_sym)
|
|
52
55
|
rescue StandardError => e
|
|
53
|
-
|
|
56
|
+
handle_exception(e, level: :debug, operation: 'AbsorbChat#parse_payload')
|
|
54
57
|
{}
|
|
55
58
|
end
|
|
56
59
|
end
|
|
@@ -14,11 +14,12 @@ module Legion
|
|
|
14
14
|
defined?(Legion::Extensions::Absorbers::Base) &&
|
|
15
15
|
defined?(Legion::Extensions::MicrosoftTeams::Absorbers::Meeting)
|
|
16
16
|
rescue StandardError => e
|
|
17
|
-
|
|
17
|
+
handle_exception(e, level: :debug, operation: 'AbsorbMeeting#enabled?')
|
|
18
18
|
false
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def work(payload)
|
|
22
|
+
log.debug("AbsorbMeeting#work payload=#{payload.inspect[0, 100]}")
|
|
22
23
|
parsed = parse_payload(payload)
|
|
23
24
|
absorber = Absorbers::Meeting.new
|
|
24
25
|
result = absorber.absorb(
|
|
@@ -30,7 +31,8 @@ module Legion
|
|
|
30
31
|
if result[:success]
|
|
31
32
|
ack!
|
|
32
33
|
else
|
|
33
|
-
|
|
34
|
+
handle_exception(RuntimeError.new(result.inspect), level: :error,
|
|
35
|
+
operation: 'AbsorbMeeting#work')
|
|
34
36
|
reject!(requeue: false)
|
|
35
37
|
end
|
|
36
38
|
else
|
|
@@ -38,19 +40,20 @@ module Legion
|
|
|
38
40
|
end
|
|
39
41
|
result
|
|
40
42
|
rescue StandardError => e
|
|
41
|
-
|
|
43
|
+
handle_exception(e, level: :error, operation: 'AbsorbMeeting#work')
|
|
42
44
|
reject!(requeue: false)
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
private
|
|
46
48
|
|
|
47
49
|
def parse_payload(payload)
|
|
50
|
+
log.debug('AbsorbMeeting#parse_payload')
|
|
48
51
|
data = payload.is_a?(String) ? json_load(payload) : payload
|
|
49
52
|
return {} unless data.is_a?(Hash)
|
|
50
53
|
|
|
51
54
|
data.transform_keys(&:to_sym)
|
|
52
55
|
rescue StandardError => e
|
|
53
|
-
|
|
56
|
+
handle_exception(e, level: :debug, operation: 'AbsorbMeeting#parse_payload')
|
|
54
57
|
{}
|
|
55
58
|
end
|
|
56
59
|
end
|
|
@@ -18,13 +18,12 @@ module Legion
|
|
|
18
18
|
def run_now? = true
|
|
19
19
|
|
|
20
20
|
def delay
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
end
|
|
21
|
+
auth_validator = Legion::Extensions::Identity::Entra::Delegated::Actor::AuthValidator.allocate
|
|
22
|
+
base_delay = auth_validator.respond_to?(:delay) ? auth_validator.delay.to_f : 9.0
|
|
23
|
+
[base_delay + 5.0, 14].max
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
handle_exception(e, level: :debug, operation: 'ApiIngest#delay')
|
|
26
|
+
14
|
|
28
27
|
end
|
|
29
28
|
|
|
30
29
|
def time
|
|
@@ -35,11 +34,12 @@ module Legion
|
|
|
35
34
|
def enabled?
|
|
36
35
|
defined?(Legion::Extensions::Agentic::Memory::Trace::Runners::Traces)
|
|
37
36
|
rescue StandardError => e
|
|
38
|
-
|
|
37
|
+
handle_exception(e, level: :warn, operation: 'ApiIngest#enabled?')
|
|
39
38
|
false
|
|
40
39
|
end
|
|
41
40
|
|
|
42
41
|
def manual
|
|
42
|
+
log.debug('ApiIngest#manual starting')
|
|
43
43
|
token = resolve_token
|
|
44
44
|
unless token
|
|
45
45
|
log.warn('ApiIngest: no delegated token, skipping')
|
|
@@ -58,7 +58,7 @@ module Legion
|
|
|
58
58
|
log.info("ApiIngest: #{result.inspect[0, 200]}")
|
|
59
59
|
result
|
|
60
60
|
rescue StandardError => e
|
|
61
|
-
|
|
61
|
+
handle_exception(e, level: :error, operation: 'ApiIngest#manual')
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
private
|
|
@@ -68,11 +68,9 @@ module Legion
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def resolve_token
|
|
71
|
-
|
|
72
|
-
Legion::Extensions::MicrosoftTeams::Helpers::TokenCache.instance.cached_delegated_token
|
|
73
|
-
end
|
|
71
|
+
Legion::Extensions::Identity::Entra::Helpers::TokenManager.load_token(:delegated)
|
|
74
72
|
rescue StandardError => e
|
|
75
|
-
|
|
73
|
+
handle_exception(e, level: :warn, operation: 'ApiIngest#resolve_token')
|
|
76
74
|
nil
|
|
77
75
|
end
|
|
78
76
|
|
|
@@ -81,7 +79,7 @@ module Legion
|
|
|
81
79
|
|
|
82
80
|
Legion::Settings[:microsoft_teams] || {}
|
|
83
81
|
rescue StandardError => e
|
|
84
|
-
|
|
82
|
+
handle_exception(e, level: :warn, operation: 'ApiIngest#teams_settings')
|
|
85
83
|
{}
|
|
86
84
|
end
|
|
87
85
|
|
|
@@ -90,7 +88,7 @@ module Legion
|
|
|
90
88
|
|
|
91
89
|
Legion::Extensions::Coldstart::Helpers::Bootstrap.new.imprint_active?
|
|
92
90
|
rescue StandardError => e
|
|
93
|
-
|
|
91
|
+
handle_exception(e, level: :debug, operation: 'ApiIngest#imprint_active?')
|
|
94
92
|
false
|
|
95
93
|
end
|
|
96
94
|
end
|
|
@@ -24,10 +24,10 @@ module Legion
|
|
|
24
24
|
def manual
|
|
25
25
|
log.info('CacheBulkIngest firing')
|
|
26
26
|
result = runner_class.ingest_cache(**args)
|
|
27
|
-
log.info("
|
|
27
|
+
log.info("CacheBulkIngest complete: #{result.inspect[0, 200]}")
|
|
28
28
|
result
|
|
29
29
|
rescue StandardError => e
|
|
30
|
-
|
|
30
|
+
handle_exception(e, level: :error, operation: 'CacheBulkIngest#manual')
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def args
|
|
@@ -41,7 +41,7 @@ module Legion
|
|
|
41
41
|
|
|
42
42
|
Legion::Extensions::Coldstart::Helpers::Bootstrap.new.imprint_active?
|
|
43
43
|
rescue StandardError => e
|
|
44
|
-
|
|
44
|
+
handle_exception(e, level: :debug, operation: 'CacheBulkIngest#imprint_active?')
|
|
45
45
|
false
|
|
46
46
|
end
|
|
47
47
|
end
|
|
@@ -33,6 +33,7 @@ module Legion
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def manual
|
|
36
|
+
log.debug('CacheSync#manual starting')
|
|
36
37
|
result = runner_class.send(runner_function, since: @last_sync_time, skip_bots: true)
|
|
37
38
|
if result.is_a?(Hash) && result[:result]
|
|
38
39
|
latest = result[:result][:latest_time]
|
|
@@ -41,7 +42,7 @@ module Legion
|
|
|
41
42
|
log.info("CacheSync: ingested #{stored} new Teams messages") if stored.positive?
|
|
42
43
|
end
|
|
43
44
|
rescue StandardError => e
|
|
44
|
-
|
|
45
|
+
handle_exception(e, level: :error, operation: 'CacheSync#manual')
|
|
45
46
|
end
|
|
46
47
|
end
|
|
47
48
|
end
|
|
@@ -36,52 +36,49 @@ module Legion
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def enabled?
|
|
39
|
-
return false unless defined?(Legion::Extensions::MicrosoftTeams::Helpers::TokenCache)
|
|
40
|
-
|
|
41
39
|
channel_setting(:enabled, false) == true
|
|
42
40
|
rescue StandardError => e
|
|
43
|
-
|
|
41
|
+
handle_exception(e, level: :debug, operation: 'ChannelPoller#enabled?')
|
|
44
42
|
false
|
|
45
43
|
end
|
|
46
44
|
|
|
47
|
-
def token_cache
|
|
48
|
-
Legion::Extensions::MicrosoftTeams::Helpers::TokenCache.instance
|
|
49
|
-
end
|
|
50
|
-
|
|
51
45
|
def manual
|
|
52
46
|
log.info('ChannelPoller polling team channels')
|
|
53
|
-
token =
|
|
47
|
+
token = delegated_token
|
|
54
48
|
unless token
|
|
55
49
|
log.debug('No token available, skipping poll')
|
|
56
50
|
return
|
|
57
51
|
end
|
|
58
52
|
|
|
59
53
|
teams = fetch_joined_teams(token: token)
|
|
60
|
-
log.debug("
|
|
54
|
+
log.debug("ChannelPoller#manual found #{teams.length} joined team(s)")
|
|
61
55
|
|
|
62
56
|
teams.first(max_teams).each do |team|
|
|
63
57
|
poll_team(team: team, token: token)
|
|
64
58
|
rescue StandardError => e
|
|
65
|
-
|
|
59
|
+
handle_exception(e, level: :error, operation: 'ChannelPoller#manual',
|
|
60
|
+
team: team['displayName'])
|
|
66
61
|
end
|
|
67
62
|
rescue StandardError => e
|
|
68
|
-
|
|
63
|
+
handle_exception(e, level: :error, operation: 'ChannelPoller#manual')
|
|
69
64
|
end
|
|
70
65
|
|
|
71
66
|
private
|
|
72
67
|
|
|
73
68
|
def fetch_joined_teams(token:)
|
|
69
|
+
log.debug('ChannelPoller#fetch_joined_teams')
|
|
74
70
|
conn = graph_connection(token: token)
|
|
75
71
|
response = conn.get('me/joinedTeams')
|
|
76
72
|
response.body&.dig('value') || []
|
|
77
73
|
rescue StandardError => e
|
|
78
|
-
|
|
74
|
+
handle_exception(e, level: :error, operation: 'ChannelPoller#fetch_joined_teams')
|
|
79
75
|
[]
|
|
80
76
|
end
|
|
81
77
|
|
|
82
78
|
def poll_team(team:, token:)
|
|
83
79
|
team_id = team['id']
|
|
84
80
|
team_name = team['displayName'] || team_id
|
|
81
|
+
log.debug("ChannelPoller#poll_team team_id=#{team_id} team_name=#{team_name}")
|
|
85
82
|
|
|
86
83
|
channels = fetch_channels(team_id: team_id, token: token)
|
|
87
84
|
selected = select_channels(channels)
|
|
@@ -89,16 +86,18 @@ module Legion
|
|
|
89
86
|
selected.first(max_channels_per_team).each do |channel|
|
|
90
87
|
poll_channel(team_id: team_id, team_name: team_name, channel: channel, token: token)
|
|
91
88
|
rescue StandardError => e
|
|
92
|
-
|
|
89
|
+
handle_exception(e, level: :error, operation: 'ChannelPoller#poll_team',
|
|
90
|
+
channel: channel['displayName'], team: team_name)
|
|
93
91
|
end
|
|
94
92
|
end
|
|
95
93
|
|
|
96
94
|
def fetch_channels(team_id:, token:)
|
|
95
|
+
log.debug("ChannelPoller#fetch_channels team_id=#{team_id}")
|
|
97
96
|
conn = graph_connection(token: token)
|
|
98
97
|
response = conn.get("teams/#{team_id}/channels")
|
|
99
98
|
response.body&.dig('value') || []
|
|
100
99
|
rescue StandardError => e
|
|
101
|
-
|
|
100
|
+
handle_exception(e, level: :error, operation: 'ChannelPoller#fetch_channels', team_id: team_id)
|
|
102
101
|
[]
|
|
103
102
|
end
|
|
104
103
|
|
|
@@ -112,6 +111,7 @@ module Legion
|
|
|
112
111
|
def poll_channel(team_id:, team_name:, channel:, token:)
|
|
113
112
|
channel_id = channel['id']
|
|
114
113
|
channel_name = channel['displayName'] || channel_id
|
|
114
|
+
log.debug("ChannelPoller#poll_channel team_id=#{team_id} channel_name=#{channel_name}")
|
|
115
115
|
|
|
116
116
|
conn = graph_connection(token: token)
|
|
117
117
|
response = conn.get(
|
|
@@ -155,12 +155,19 @@ module Legion
|
|
|
155
155
|
channel_setting(:max_channels_per_team, DEFAULT_MAX_CHANNELS)
|
|
156
156
|
end
|
|
157
157
|
|
|
158
|
+
def delegated_token
|
|
159
|
+
Legion::Extensions::Identity::Entra::Helpers::TokenManager.load_token(:delegated)
|
|
160
|
+
rescue StandardError => e
|
|
161
|
+
handle_exception(e, level: :warn, operation: 'ChannelPoller#delegated_token')
|
|
162
|
+
nil
|
|
163
|
+
end
|
|
164
|
+
|
|
158
165
|
def channel_setting(key, default)
|
|
159
166
|
return default unless defined?(Legion::Settings)
|
|
160
167
|
|
|
161
168
|
Legion::Settings.dig(:microsoft_teams, :channels, key) || default
|
|
162
169
|
rescue StandardError => e
|
|
163
|
-
|
|
170
|
+
handle_exception(e, level: :debug, operation: "ChannelPoller#channel_setting(#{key})")
|
|
164
171
|
default
|
|
165
172
|
end
|
|
166
173
|
|
|
@@ -169,6 +176,7 @@ module Legion
|
|
|
169
176
|
end
|
|
170
177
|
|
|
171
178
|
def store_channel_message_trace(team_name:, channel_name:, msg:)
|
|
179
|
+
log.debug("ChannelPoller#store_channel_message_trace team=#{team_name} channel=#{channel_name}")
|
|
172
180
|
sender = msg.dig('from', 'user', 'displayName') || 'Unknown'
|
|
173
181
|
content = (msg.dig('body', 'content') || '').gsub(/<[^>]+>/, '').strip
|
|
174
182
|
memory_runner.store_trace(
|
|
@@ -179,7 +187,8 @@ module Legion
|
|
|
179
187
|
confidence: 0.7
|
|
180
188
|
)
|
|
181
189
|
rescue StandardError => e
|
|
182
|
-
|
|
190
|
+
handle_exception(e, level: :error, operation: 'ChannelPoller#store_channel_message_trace',
|
|
191
|
+
team: team_name, channel: channel_name)
|
|
183
192
|
end
|
|
184
193
|
end
|
|
185
194
|
end
|
|
@@ -30,16 +30,13 @@ module Legion
|
|
|
30
30
|
defined?(Legion::Extensions::MicrosoftTeams::Runners::Bot) &&
|
|
31
31
|
Legion.const_defined?(:Transport, false)
|
|
32
32
|
rescue StandardError => e
|
|
33
|
-
|
|
33
|
+
handle_exception(e, level: :debug, operation: 'DirectChatPoller#enabled?')
|
|
34
34
|
false
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
def token_cache
|
|
38
|
-
Legion::Extensions::MicrosoftTeams::Helpers::TokenCache.instance
|
|
39
|
-
end
|
|
40
|
-
|
|
41
37
|
def manual
|
|
42
|
-
|
|
38
|
+
log.debug('DirectChatPoller#manual starting')
|
|
39
|
+
token = delegated_token
|
|
43
40
|
unless token
|
|
44
41
|
log.debug('No token available, skipping poll')
|
|
45
42
|
return
|
|
@@ -47,21 +44,23 @@ module Legion
|
|
|
47
44
|
|
|
48
45
|
log.info('Polling bot DM chats')
|
|
49
46
|
chats = fetch_bot_chats(token: token)
|
|
50
|
-
log.info("
|
|
47
|
+
log.info("DirectChatPoller found #{chats.length} bot chats")
|
|
51
48
|
chats.each { |chat| poll_chat(chat_id: chat[:id], token: token) }
|
|
52
49
|
rescue StandardError => e
|
|
53
|
-
|
|
50
|
+
handle_exception(e, level: :error, operation: 'DirectChatPoller#manual')
|
|
54
51
|
end
|
|
55
52
|
|
|
56
53
|
private
|
|
57
54
|
|
|
58
55
|
def fetch_bot_chats(token:)
|
|
56
|
+
log.debug('DirectChatPoller#fetch_bot_chats')
|
|
59
57
|
conn = graph_connection(token: token)
|
|
60
58
|
response = conn.get('me/chats', { '$filter' => "chatType eq 'oneOnOne'", '$top' => 50 })
|
|
61
59
|
response.body&.dig('value') || []
|
|
62
60
|
end
|
|
63
61
|
|
|
64
62
|
def poll_chat(chat_id:, token:)
|
|
63
|
+
log.debug("DirectChatPoller#poll_chat chat_id=#{chat_id}")
|
|
65
64
|
conn = graph_connection(token: token)
|
|
66
65
|
response = conn.get("chats/#{chat_id}/messages",
|
|
67
66
|
{ '$top' => 10, '$orderby' => 'createdDateTime desc' })
|
|
@@ -71,7 +70,7 @@ module Legion
|
|
|
71
70
|
new_msgs.reject! { |m| m[:from_id] == @bot_id }
|
|
72
71
|
return if new_msgs.empty?
|
|
73
72
|
|
|
74
|
-
log.info("
|
|
73
|
+
log.info("DirectChatPoller chat #{chat_id}: #{new_msgs.length} new message(s)")
|
|
75
74
|
new_msgs.each { |msg| publish_message(msg.merge(chat_id: chat_id, mode: :direct)) }
|
|
76
75
|
update_hwm_from_messages(chat_id: chat_id, messages: new_msgs)
|
|
77
76
|
end
|
|
@@ -79,7 +78,7 @@ module Legion
|
|
|
79
78
|
def publish_message(payload)
|
|
80
79
|
Legion::Extensions::MicrosoftTeams::Transport::Messages::TeamsMessage.new.publish(payload)
|
|
81
80
|
rescue StandardError => e
|
|
82
|
-
|
|
81
|
+
handle_exception(e, level: :error, operation: 'DirectChatPoller#publish_message')
|
|
83
82
|
end
|
|
84
83
|
|
|
85
84
|
def normalize_messages(messages)
|
|
@@ -101,6 +100,13 @@ module Legion
|
|
|
101
100
|
Legion::Settings.dig(:microsoft_teams, :bot, :bot_id)
|
|
102
101
|
end
|
|
103
102
|
|
|
103
|
+
def delegated_token
|
|
104
|
+
Legion::Extensions::Identity::Entra::Helpers::TokenManager.load_token(:delegated)
|
|
105
|
+
rescue StandardError => e
|
|
106
|
+
handle_exception(e, level: :warn, operation: 'DirectChatPoller#delegated_token')
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
|
|
104
110
|
def settings_interval(key, default)
|
|
105
111
|
return default unless defined?(Legion::Settings)
|
|
106
112
|
|
|
@@ -17,7 +17,7 @@ module Legion
|
|
|
17
17
|
settings = begin
|
|
18
18
|
Legion::Settings[:microsoft_teams] || {}
|
|
19
19
|
rescue StandardError => e
|
|
20
|
-
|
|
20
|
+
handle_exception(e, level: :debug, operation: 'IncrementalSync#time')
|
|
21
21
|
{}
|
|
22
22
|
end
|
|
23
23
|
settings.dig(:ingest, :incremental_interval) || 120
|
|
@@ -27,17 +27,19 @@ module Legion
|
|
|
27
27
|
defined?(Legion::Extensions::Agentic::Memory::Trace::Runners::Traces) &&
|
|
28
28
|
token_available?
|
|
29
29
|
rescue StandardError => e
|
|
30
|
-
|
|
30
|
+
handle_exception(e, level: :debug, operation: 'IncrementalSync#enabled?')
|
|
31
31
|
false
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def manual
|
|
35
|
+
log.debug('IncrementalSync#manual starting')
|
|
35
36
|
token = resolve_token
|
|
36
37
|
return unless token
|
|
37
38
|
|
|
38
39
|
settings = begin
|
|
39
40
|
Legion::Settings[:microsoft_teams] || {}
|
|
40
|
-
rescue StandardError =>
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
handle_exception(e, level: :debug, operation: 'IncrementalSync#manual settings')
|
|
41
43
|
{}
|
|
42
44
|
end
|
|
43
45
|
ingest = settings[:ingest] || {}
|
|
@@ -47,7 +49,7 @@ module Legion
|
|
|
47
49
|
message_depth: ingest.fetch(:message_depth, 50)
|
|
48
50
|
)
|
|
49
51
|
rescue StandardError => e
|
|
50
|
-
|
|
52
|
+
handle_exception(e, level: :error, operation: 'IncrementalSync#manual')
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
private
|
|
@@ -57,11 +59,9 @@ module Legion
|
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
def resolve_token
|
|
60
|
-
|
|
61
|
-
Legion::Extensions::MicrosoftTeams::Helpers::TokenCache.instance.cached_delegated_token
|
|
62
|
-
end
|
|
62
|
+
Legion::Extensions::Identity::Entra::Helpers::TokenManager.load_token(:delegated)
|
|
63
63
|
rescue StandardError => e
|
|
64
|
-
|
|
64
|
+
handle_exception(e, level: :warn, operation: 'IncrementalSync#resolve_token')
|
|
65
65
|
nil
|
|
66
66
|
end
|
|
67
67
|
end
|