lex-microsoft_teams 0.5.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/.github/workflows/ci.yml +16 -0
- data/.gitignore +15 -0
- data/.rspec +3 -0
- data/.rubocop.yml +56 -0
- data/CHANGELOG.md +59 -0
- data/CLAUDE.md +206 -0
- data/Dockerfile +6 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +103 -0
- data/LICENSE +21 -0
- data/README.md +183 -0
- data/docs/plans/2026-03-15-meetings-transcripts-design.md +506 -0
- data/docs/plans/2026-03-16-delegated-oauth-browser-flow-design.md +198 -0
- data/docs/plans/2026-03-16-delegated-oauth-browser-flow-plan.md +1176 -0
- data/lex-microsoft_teams.gemspec +32 -0
- data/lib/legion/extensions/microsoft_teams/actors/cache_bulk_ingest.rb +41 -0
- data/lib/legion/extensions/microsoft_teams/actors/cache_sync.rb +54 -0
- data/lib/legion/extensions/microsoft_teams/actors/direct_chat_poller.rb +105 -0
- data/lib/legion/extensions/microsoft_teams/actors/message_processor.rb +23 -0
- data/lib/legion/extensions/microsoft_teams/actors/observed_chat_poller.rb +111 -0
- data/lib/legion/extensions/microsoft_teams/client.rb +68 -0
- data/lib/legion/extensions/microsoft_teams/helpers/browser_auth.rb +139 -0
- data/lib/legion/extensions/microsoft_teams/helpers/callback_server.rb +82 -0
- data/lib/legion/extensions/microsoft_teams/helpers/client.rb +38 -0
- data/lib/legion/extensions/microsoft_teams/helpers/high_water_mark.rb +59 -0
- data/lib/legion/extensions/microsoft_teams/helpers/prompt_resolver.rb +64 -0
- data/lib/legion/extensions/microsoft_teams/helpers/session_manager.rb +150 -0
- data/lib/legion/extensions/microsoft_teams/helpers/subscription_registry.rb +140 -0
- data/lib/legion/extensions/microsoft_teams/helpers/token_cache.rb +209 -0
- data/lib/legion/extensions/microsoft_teams/local_cache/extractor.rb +258 -0
- data/lib/legion/extensions/microsoft_teams/local_cache/record_parser.rb +199 -0
- data/lib/legion/extensions/microsoft_teams/local_cache/sstable_reader.rb +121 -0
- data/lib/legion/extensions/microsoft_teams/runners/adaptive_cards.rb +59 -0
- data/lib/legion/extensions/microsoft_teams/runners/auth.rb +116 -0
- data/lib/legion/extensions/microsoft_teams/runners/bot.rb +409 -0
- data/lib/legion/extensions/microsoft_teams/runners/cache_ingest.rb +122 -0
- data/lib/legion/extensions/microsoft_teams/runners/channel_messages.rb +52 -0
- data/lib/legion/extensions/microsoft_teams/runners/channels.rb +53 -0
- data/lib/legion/extensions/microsoft_teams/runners/chats.rb +51 -0
- data/lib/legion/extensions/microsoft_teams/runners/local_cache.rb +62 -0
- data/lib/legion/extensions/microsoft_teams/runners/meetings.rb +68 -0
- data/lib/legion/extensions/microsoft_teams/runners/messages.rb +48 -0
- data/lib/legion/extensions/microsoft_teams/runners/presence.rb +31 -0
- data/lib/legion/extensions/microsoft_teams/runners/subscriptions.rb +76 -0
- data/lib/legion/extensions/microsoft_teams/runners/teams.rb +33 -0
- data/lib/legion/extensions/microsoft_teams/runners/transcripts.rb +45 -0
- data/lib/legion/extensions/microsoft_teams/transport/exchanges/messages.rb +15 -0
- data/lib/legion/extensions/microsoft_teams/transport/messages/teams_message.rb +16 -0
- data/lib/legion/extensions/microsoft_teams/transport/queues/messages_process.rb +16 -0
- data/lib/legion/extensions/microsoft_teams/version.rb +9 -0
- data/lib/legion/extensions/microsoft_teams.rb +44 -0
- metadata +139 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c9f65eaac56b416e05eb5d96d914d6e3a88b86c91fdd083162fa09e5a6c887f4
|
|
4
|
+
data.tar.gz: ba6cd5f7d0d1b08cc8bc880cd8ab0db61d799ba66afaca8ae1938df057149a16
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e996a958fbffca134f301089fcc9c3be7abd03ef73f7b756f975198d3653dcbefaaa53a3ddf26c3afd33c92aea574f33336200604912088383e7ac6acdff54d5
|
|
7
|
+
data.tar.gz: 800d036ddcb4a99ee833b3abb3ff255a60f1102625ed8ef1ec0ea9ad90c0e0195b654b8b14a0fff41835eb4288191c62fd0aaf03fcb8b58b087c752b1c32f20e
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
ci:
|
|
9
|
+
uses: LegionIO/.github/.github/workflows/ci.yml@main
|
|
10
|
+
|
|
11
|
+
release:
|
|
12
|
+
needs: ci
|
|
13
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
14
|
+
uses: LegionIO/.github/.github/workflows/release.yml@main
|
|
15
|
+
secrets:
|
|
16
|
+
rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.4
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
|
|
6
|
+
Layout/LineLength:
|
|
7
|
+
Max: 160
|
|
8
|
+
|
|
9
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
|
10
|
+
EnforcedStyle: space
|
|
11
|
+
|
|
12
|
+
Layout/HashAlignment:
|
|
13
|
+
EnforcedHashRocketStyle: table
|
|
14
|
+
EnforcedColonStyle: table
|
|
15
|
+
|
|
16
|
+
Metrics/MethodLength:
|
|
17
|
+
Max: 50
|
|
18
|
+
|
|
19
|
+
Metrics/ClassLength:
|
|
20
|
+
Max: 1500
|
|
21
|
+
|
|
22
|
+
Metrics/ModuleLength:
|
|
23
|
+
Max: 1500
|
|
24
|
+
|
|
25
|
+
Metrics/BlockLength:
|
|
26
|
+
Max: 40
|
|
27
|
+
Exclude:
|
|
28
|
+
- 'spec/**/*'
|
|
29
|
+
|
|
30
|
+
Metrics/ParameterLists:
|
|
31
|
+
Max: 8
|
|
32
|
+
|
|
33
|
+
Metrics/AbcSize:
|
|
34
|
+
Max: 60
|
|
35
|
+
|
|
36
|
+
Metrics/CyclomaticComplexity:
|
|
37
|
+
Max: 15
|
|
38
|
+
|
|
39
|
+
Metrics/PerceivedComplexity:
|
|
40
|
+
Max: 17
|
|
41
|
+
|
|
42
|
+
Style/Documentation:
|
|
43
|
+
Enabled: false
|
|
44
|
+
|
|
45
|
+
Style/SymbolArray:
|
|
46
|
+
Enabled: true
|
|
47
|
+
|
|
48
|
+
Style/FrozenStringLiteralComment:
|
|
49
|
+
Enabled: true
|
|
50
|
+
EnforcedStyle: always
|
|
51
|
+
|
|
52
|
+
Naming/FileName:
|
|
53
|
+
Enabled: false
|
|
54
|
+
|
|
55
|
+
Naming/PredicateMethod:
|
|
56
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.5.0] - 2026-03-16
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Delegated OAuth browser flow with Authorization Code + PKCE
|
|
7
|
+
- Automatic Device Code fallback for headless environments
|
|
8
|
+
- `Helpers::BrowserAuth` orchestrator (PKCE generation, headless detection, browser opening)
|
|
9
|
+
- `Helpers::CallbackServer` ephemeral TCP server for OAuth redirect
|
|
10
|
+
- `Runners::Auth#authorize_url`, `#exchange_code`, `#refresh_delegated_token` methods
|
|
11
|
+
- `Helpers::TokenCache` delegated token slot with Vault persistence and silent refresh
|
|
12
|
+
- `legion auth teams` CLI command (in LegionIO) for interactive authentication
|
|
13
|
+
- `GET /api/oauth/microsoft_teams/callback` Sinatra route (in LegionIO) for daemon re-auth
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- `poll_device_code` now persists `slow_down` interval increase per RFC 8628
|
|
17
|
+
- `poll_device_code` returns error hash on timeout instead of raising RuntimeError
|
|
18
|
+
|
|
19
|
+
## [0.4.1] - 2026-03-15
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- Preference commands: `prefer <value>`, `preferences`, `reset preferences`
|
|
23
|
+
- PromptResolver queries PreferenceProfile for per-user system prompt customization
|
|
24
|
+
- SessionManager passes owner_id through to PromptResolver
|
|
25
|
+
- `SessionManager#refresh_prompt` rebuilds system prompt without clearing history
|
|
26
|
+
|
|
27
|
+
## [0.4.0] - 2026-03-15
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- Meetings runner: list, get, create, update, delete online meetings, lookup by join URL, attendance reports
|
|
31
|
+
- Transcripts runner: list, get metadata, get content (VTT/DOCX format support)
|
|
32
|
+
- New Graph API permissions: `OnlineMeeting.Read.All`, `OnlineMeetingTranscript.Read.All`
|
|
33
|
+
|
|
34
|
+
## [0.3.0] - 2026-03-15
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
- Token cache helper with 60-second pre-expiry automatic refresh
|
|
38
|
+
- Subscription registry with in-memory store and lex-memory persistence
|
|
39
|
+
- Command handler for bot DMs: watch, unwatch, list, pause, resume
|
|
40
|
+
- Token cache wired into DirectChatPoller and ObservedChatPoller
|
|
41
|
+
- Subscription registry wired into ObservedChatPoller
|
|
42
|
+
|
|
43
|
+
## [0.2.0] - 2026-03-15
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
- AI bot with direct chat mode (LLM-powered 1:1 responses via polling)
|
|
47
|
+
- Conversation observer mode (task/context extraction from watched chats, default disabled)
|
|
48
|
+
- AMQP-based message routing (teams.messages exchange and queue)
|
|
49
|
+
- Session manager with lex-memory persistence for multi-turn conversations
|
|
50
|
+
- Layered prompt resolver (settings -> mode -> per-conversation overrides)
|
|
51
|
+
- High-water mark tracking for message deduplication
|
|
52
|
+
- DirectChatPoller actor (5s interval, Graph API polling)
|
|
53
|
+
- ObservedChatPoller actor (30s interval, compliance-gated)
|
|
54
|
+
- MessageProcessor subscription actor (AMQP consumer, routes by mode)
|
|
55
|
+
|
|
56
|
+
## [0.1.0] - 2026-03-13
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
- Initial release
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# lex-microsoft_teams: Microsoft Teams Integration for LegionIO
|
|
2
|
+
|
|
3
|
+
**Repository Level 3 Documentation**
|
|
4
|
+
- **Parent (Level 2)**: `/Users/miverso2/rubymine/legion/extensions/CLAUDE.md`
|
|
5
|
+
- **Parent (Level 1)**: `/Users/miverso2/rubymine/legion/CLAUDE.md`
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Legion Extension that connects LegionIO to Microsoft Teams via Graph API and Bot Framework. Provides runners for chats, channels, messages, subscriptions (change notifications), adaptive cards, bot communication, and an AI-powered bot with conversation observation.
|
|
10
|
+
|
|
11
|
+
**GitHub**: https://github.com/LegionIO/lex-microsoft_teams
|
|
12
|
+
**License**: MIT
|
|
13
|
+
**Version**: 0.5.0
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Legion::Extensions::MicrosoftTeams
|
|
19
|
+
├── Runners/
|
|
20
|
+
│ ├── Auth # OAuth2 client credentials (Graph + Bot Framework)
|
|
21
|
+
│ ├── Teams # List/get teams, members
|
|
22
|
+
│ ├── Chats # 1:1 and group chat CRUD
|
|
23
|
+
│ ├── Messages # Chat message send/read/reply
|
|
24
|
+
│ ├── Channels # Team channel CRUD
|
|
25
|
+
│ ├── ChannelMessages # Channel message send/read/reply
|
|
26
|
+
│ ├── Subscriptions # Graph change notification webhooks
|
|
27
|
+
│ ├── AdaptiveCards # Adaptive Card payload builder
|
|
28
|
+
│ ├── Bot # Bot Framework + AI bot (handle_message, handle_command, observe_message)
|
|
29
|
+
│ ├── Presence # Graph API user presence
|
|
30
|
+
│ ├── Meetings # Online meeting CRUD, join URL lookup, attendance reports
|
|
31
|
+
│ ├── Transcripts # Meeting transcript list/get/content (VTT/DOCX)
|
|
32
|
+
│ ├── LocalCache # Offline message extraction from local LevelDB cache
|
|
33
|
+
│ └── CacheIngest # Ingest cached messages into lex-memory as episodic traces
|
|
34
|
+
├── Actors/
|
|
35
|
+
│ ├── CacheBulkIngest # Once: full cache ingest at startup (imprint window support)
|
|
36
|
+
│ ├── CacheSync # Every 5min: incremental ingest of new messages
|
|
37
|
+
│ ├── DirectChatPoller # Every 5s: polls bot DM chats via Graph API
|
|
38
|
+
│ ├── ObservedChatPoller # Every 30s: polls subscribed human conversations (compliance-gated)
|
|
39
|
+
│ └── MessageProcessor # Subscription: consumes AMQP queue, routes by mode
|
|
40
|
+
├── Transport/
|
|
41
|
+
│ ├── Exchanges/Messages # teams.messages topic exchange
|
|
42
|
+
│ ├── Queues/MessagesProcess # teams.messages.process durable queue
|
|
43
|
+
│ └── Messages/TeamsMessage # Message schema with routing key
|
|
44
|
+
├── LocalCache/
|
|
45
|
+
│ ├── SSTableReader # Pure Ruby LevelDB .ldb file reader (Snappy decompression)
|
|
46
|
+
│ ├── RecordParser # Chromium IndexedDB value parser (field-value pairing)
|
|
47
|
+
│ └── Extractor # Message extraction, filtering, dedup from local cache
|
|
48
|
+
├── Helpers/
|
|
49
|
+
│ ├── Client # Three connection builders (Graph, Bot, OAuth)
|
|
50
|
+
│ ├── HighWaterMark # Per-chat message dedup via legion-cache (with in-memory fallback)
|
|
51
|
+
│ ├── PromptResolver # Layered system prompt resolution (settings -> mode -> per-conversation)
|
|
52
|
+
│ ├── SessionManager # Multi-turn LLM session lifecycle with lex-memory persistence
|
|
53
|
+
│ ├── TokenCache # In-memory OAuth token cache with pre-expiry refresh (app + delegated slots)
|
|
54
|
+
│ ├── SubscriptionRegistry # Conversation observation subscriptions (in-memory + lex-memory)
|
|
55
|
+
│ ├── BrowserAuth # Delegated OAuth orchestrator (PKCE, headless detection, browser launch)
|
|
56
|
+
│ └── CallbackServer # Ephemeral TCP server for OAuth redirect callback
|
|
57
|
+
└── Client # Standalone client (includes all runners)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Delegated Authentication (v0.5.0)
|
|
61
|
+
|
|
62
|
+
Opt-in browser-based OAuth for delegated Microsoft Graph permissions. Two flows:
|
|
63
|
+
|
|
64
|
+
- **Authorization Code + PKCE** (primary): Opens browser for Entra ID login, captures callback on ephemeral local port, exchanges code with PKCE verification
|
|
65
|
+
- **Device Code** (fallback): Auto-selected in headless/SSH environments (no `DISPLAY`/`WAYLAND_DISPLAY`)
|
|
66
|
+
|
|
67
|
+
Tokens stored in Vault (`legionio/microsoft_teams/delegated_token`) with configurable pre-expiry silent refresh. CLI command: `legion auth teams`. Sinatra route: `GET /api/oauth/microsoft_teams/callback` for daemon re-auth.
|
|
68
|
+
|
|
69
|
+
Key files: `Helpers::BrowserAuth` (orchestrator), `Helpers::CallbackServer` (ephemeral TCP), `Runners::Auth` (authorize_url, exchange_code, refresh_delegated_token), `Helpers::TokenCache` (delegated slot).
|
|
70
|
+
|
|
71
|
+
## AI Bot (v0.2.0)
|
|
72
|
+
|
|
73
|
+
Two operating modes, both using polling (Graph API) with AMQP-based message routing:
|
|
74
|
+
|
|
75
|
+
### Mode 1: Direct Chat
|
|
76
|
+
User DMs the bot 1:1. Bot responds via legion-llm with multi-turn session context.
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
DirectChatPoller (5s) → AMQP exchange → MessageProcessor → Bot::handle_message
|
|
80
|
+
→ SessionManager.get_or_create → llm_session.ask(text) → Graph API reply
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Mode 2: Conversation Observer
|
|
84
|
+
User subscribes the bot to watch a human 1:1 conversation. Bot passively extracts tasks, context, and relationship data.
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
ObservedChatPoller (30s) → AMQP exchange → MessageProcessor → Bot::observe_message
|
|
88
|
+
→ LLM extraction → lex-memory episodic trace → optional notification to owner
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Observer is disabled by default** (`settings[:bot][:observe][:enabled] = false`). Compliance gate — must be explicitly enabled.
|
|
92
|
+
|
|
93
|
+
### Message Flow
|
|
94
|
+
|
|
95
|
+
Both pollers publish to the same `teams.messages` AMQP exchange. The MessageProcessor subscription actor consumes from the queue and routes by `mode` field (`:direct` → `handle_message`, `:observe` → `observe_message`). This architecture supports a future webhook path: a `POST /api/hooks/microsoft_teams/bot` endpoint would publish to the same exchange with zero runner changes.
|
|
96
|
+
|
|
97
|
+
### Configuration
|
|
98
|
+
|
|
99
|
+
Layered config cascade in `Legion::Settings[:microsoft_teams]`:
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
microsoft_teams:
|
|
103
|
+
auth:
|
|
104
|
+
tenant_id: "..."
|
|
105
|
+
client_id: "..."
|
|
106
|
+
client_secret: "vault://secret/teams/client_secret"
|
|
107
|
+
bot:
|
|
108
|
+
bot_id: "28:your-bot-id"
|
|
109
|
+
direct_poll_interval: 5 # seconds
|
|
110
|
+
observe_poll_interval: 30 # seconds
|
|
111
|
+
system_prompt: "You are a helpful assistant."
|
|
112
|
+
direct:
|
|
113
|
+
system_prompt: ~ # nil = inherit base
|
|
114
|
+
observe:
|
|
115
|
+
enabled: false # compliance gate
|
|
116
|
+
notify: false # DM notifications for action items
|
|
117
|
+
system_prompt: "Extract action items. Return structured JSON."
|
|
118
|
+
llm:
|
|
119
|
+
model: ~ # nil = use legion-llm router
|
|
120
|
+
intent:
|
|
121
|
+
capability: moderate
|
|
122
|
+
session:
|
|
123
|
+
flush_threshold: 20 # messages before auto-persist
|
|
124
|
+
idle_timeout: 900 # seconds (15 min)
|
|
125
|
+
max_recent_messages: 5 # kept raw on persist
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Per-conversation overrides stored in lex-memory (system_prompt_append, llm model/intent).
|
|
129
|
+
|
|
130
|
+
### Key Design Decisions
|
|
131
|
+
|
|
132
|
+
- **Polling first, webhook later**: All connections outbound from user's local LegionIO instance. No public endpoint needed.
|
|
133
|
+
- **AMQP-first routing**: Pollers and future webhooks publish to the same exchange. Decouples ingestion from processing.
|
|
134
|
+
- **High-water marks**: Per-chat last-seen timestamp in legion-cache prevents reprocessing. Falls back to in-memory when cache unavailable.
|
|
135
|
+
- **Session persistence**: Multi-turn sessions flush to lex-memory on threshold (20 msgs), idle timeout (15 min), or shutdown. Restored on restart via summary + recent messages.
|
|
136
|
+
- **Token caching**: In-memory OAuth token cache refreshes 60 seconds before expiry. Both pollers share a `TokenCache` instance instead of hitting OAuth every cycle.
|
|
137
|
+
- **Subscription registry**: In-memory working set of observed conversations, persisted to lex-memory on change. No legion-data migration needed.
|
|
138
|
+
- **Design docs**: `docs/plans/2026-03-15-teams-ai-bot-design.md`, `docs/plans/2026-03-15-teams-bot-commands-design.md`
|
|
139
|
+
|
|
140
|
+
### Bot Commands (v0.3.0)
|
|
141
|
+
|
|
142
|
+
Keyword-based command detection in bot DMs, checked before LLM response:
|
|
143
|
+
|
|
144
|
+
| Command | Action |
|
|
145
|
+
|---------|--------|
|
|
146
|
+
| `watch <name>` | Find chat via Graph API, subscribe to observe |
|
|
147
|
+
| `stop watching <name>` / `unwatch <name>` | Unsubscribe |
|
|
148
|
+
| `watching` / `list` / `subscriptions` | List active subscriptions |
|
|
149
|
+
| `pause <name>` | Disable subscription temporarily |
|
|
150
|
+
| `resume <name>` | Re-enable paused subscription |
|
|
151
|
+
| `prefer <value>` | Set preference (concise, detailed, formal, casual, etc.) |
|
|
152
|
+
| `preferences` / `my preferences` | Show current resolved preferences |
|
|
153
|
+
| `reset preferences` | Clear explicit preferences, fall back to observed/defaults |
|
|
154
|
+
| anything else | LLM response (existing flow) |
|
|
155
|
+
|
|
156
|
+
## API Surface
|
|
157
|
+
|
|
158
|
+
Four distinct APIs accessed via Faraday + one local data source:
|
|
159
|
+
- **Microsoft Graph API** (`graph.microsoft.com/v1.0`) — chats, channels, messages, teams, subscriptions, presence
|
|
160
|
+
- **Bot Framework Service** (`service_url` per conversation) — send activities, create conversations
|
|
161
|
+
- **Entra ID OAuth** (`login.microsoftonline.com`) — client_credentials token acquisition
|
|
162
|
+
- **Local LevelDB Cache** (Chromium IndexedDB) — offline message extraction from Teams 2.x local storage
|
|
163
|
+
|
|
164
|
+
## Graph API Permissions Required
|
|
165
|
+
|
|
166
|
+
| Permission | Type | Purpose |
|
|
167
|
+
|-----------|------|---------|
|
|
168
|
+
| `Chat.Read.All` | Application | Read chat messages |
|
|
169
|
+
| `Chat.ReadWrite.All` | Application | Send chat messages |
|
|
170
|
+
| `ChannelMessage.Read.All` | Application | Read channel messages |
|
|
171
|
+
| `ChannelMessage.Send` | Delegated | Send channel messages |
|
|
172
|
+
| `Team.ReadBasic.All` | Application | List teams and members |
|
|
173
|
+
| `Channel.ReadBasic.All` | Application | List channels |
|
|
174
|
+
| `Presence.Read.All` | Application | Read user presence |
|
|
175
|
+
| `OnlineMeetings.Read` | Delegated | Read online meetings (user context) |
|
|
176
|
+
| `OnlineMeetings.Read.All` | Application | Read online meetings |
|
|
177
|
+
| `OnlineMeetingTranscript.Read.All` | Application/Delegated | Read meeting transcripts |
|
|
178
|
+
|
|
179
|
+
For bot scenarios, register the Entra app as a Teams Bot via Bot Framework portal.
|
|
180
|
+
|
|
181
|
+
## Dependencies
|
|
182
|
+
|
|
183
|
+
| Gem | Purpose |
|
|
184
|
+
|-----|---------|
|
|
185
|
+
| `faraday` (>= 2.0) | HTTP client for Graph API, Bot Framework, and OAuth |
|
|
186
|
+
| `snappy` (>= 0.5) | Snappy decompression for LevelDB SSTable blocks |
|
|
187
|
+
| `base64` (>= 0.1) | Base64 encoding for PKCE (removed from Ruby 3.4 default gems) |
|
|
188
|
+
|
|
189
|
+
Optional framework dependencies (guarded with `defined?`, not in gemspec):
|
|
190
|
+
- `legion-transport` — AMQP exchange/queue/message for bot message routing
|
|
191
|
+
- `legion-llm` — LLM routing for bot responses (`llm_chat`, `llm_session`)
|
|
192
|
+
- `legion-cache` — High-water mark storage for message dedup
|
|
193
|
+
- `lex-memory` — Session persistence and episodic trace storage
|
|
194
|
+
- `lex-mesh` — PreferenceProfile for per-user preference resolution
|
|
195
|
+
|
|
196
|
+
## Testing
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
bundle install
|
|
200
|
+
bundle exec rspec # 185 specs (as of v0.5.0)
|
|
201
|
+
bundle exec rubocop # Clean
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
**Maintained By**: Matthew Iverson (@Esity)
|
data/Dockerfile
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
lex-microsoft_teams (0.5.0)
|
|
5
|
+
base64 (>= 0.1)
|
|
6
|
+
faraday (>= 2.0)
|
|
7
|
+
snappy (>= 0.5)
|
|
8
|
+
|
|
9
|
+
GEM
|
|
10
|
+
remote: https://rubygems.org/
|
|
11
|
+
specs:
|
|
12
|
+
addressable (2.8.9)
|
|
13
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
14
|
+
ast (2.4.3)
|
|
15
|
+
base64 (0.3.0)
|
|
16
|
+
bigdecimal (4.0.1)
|
|
17
|
+
diff-lcs (1.6.2)
|
|
18
|
+
docile (1.4.1)
|
|
19
|
+
faraday (2.14.1)
|
|
20
|
+
faraday-net_http (>= 2.0, < 3.5)
|
|
21
|
+
json
|
|
22
|
+
logger
|
|
23
|
+
faraday-net_http (3.4.2)
|
|
24
|
+
net-http (~> 0.5)
|
|
25
|
+
json (2.19.1)
|
|
26
|
+
json-schema (6.2.0)
|
|
27
|
+
addressable (~> 2.8)
|
|
28
|
+
bigdecimal (>= 3.1, < 5)
|
|
29
|
+
language_server-protocol (3.17.0.5)
|
|
30
|
+
lint_roller (1.1.0)
|
|
31
|
+
logger (1.7.0)
|
|
32
|
+
mcp (0.8.0)
|
|
33
|
+
json-schema (>= 4.1)
|
|
34
|
+
net-http (0.9.1)
|
|
35
|
+
uri (>= 0.11.1)
|
|
36
|
+
parallel (1.27.0)
|
|
37
|
+
parser (3.3.10.2)
|
|
38
|
+
ast (~> 2.4.1)
|
|
39
|
+
racc
|
|
40
|
+
prism (1.9.0)
|
|
41
|
+
public_suffix (7.0.5)
|
|
42
|
+
racc (1.8.1)
|
|
43
|
+
rainbow (3.1.1)
|
|
44
|
+
rake (13.3.1)
|
|
45
|
+
regexp_parser (2.11.3)
|
|
46
|
+
rspec (3.13.2)
|
|
47
|
+
rspec-core (~> 3.13.0)
|
|
48
|
+
rspec-expectations (~> 3.13.0)
|
|
49
|
+
rspec-mocks (~> 3.13.0)
|
|
50
|
+
rspec-core (3.13.6)
|
|
51
|
+
rspec-support (~> 3.13.0)
|
|
52
|
+
rspec-expectations (3.13.5)
|
|
53
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
54
|
+
rspec-support (~> 3.13.0)
|
|
55
|
+
rspec-mocks (3.13.8)
|
|
56
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
57
|
+
rspec-support (~> 3.13.0)
|
|
58
|
+
rspec-support (3.13.7)
|
|
59
|
+
rspec_junit_formatter (0.6.0)
|
|
60
|
+
rspec-core (>= 2, < 4, != 2.12.0)
|
|
61
|
+
rubocop (1.85.1)
|
|
62
|
+
json (~> 2.3)
|
|
63
|
+
language_server-protocol (~> 3.17.0.2)
|
|
64
|
+
lint_roller (~> 1.1.0)
|
|
65
|
+
mcp (~> 0.6)
|
|
66
|
+
parallel (~> 1.10)
|
|
67
|
+
parser (>= 3.3.0.2)
|
|
68
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
69
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
70
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
71
|
+
ruby-progressbar (~> 1.7)
|
|
72
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
73
|
+
rubocop-ast (1.49.1)
|
|
74
|
+
parser (>= 3.3.7.2)
|
|
75
|
+
prism (~> 1.7)
|
|
76
|
+
ruby-progressbar (1.13.0)
|
|
77
|
+
simplecov (0.22.0)
|
|
78
|
+
docile (~> 1.1)
|
|
79
|
+
simplecov-html (~> 0.11)
|
|
80
|
+
simplecov_json_formatter (~> 0.1)
|
|
81
|
+
simplecov-html (0.13.2)
|
|
82
|
+
simplecov_json_formatter (0.1.4)
|
|
83
|
+
snappy (0.5.0)
|
|
84
|
+
unicode-display_width (3.2.0)
|
|
85
|
+
unicode-emoji (~> 4.1)
|
|
86
|
+
unicode-emoji (4.2.0)
|
|
87
|
+
uri (1.1.1)
|
|
88
|
+
|
|
89
|
+
PLATFORMS
|
|
90
|
+
arm64-darwin-25
|
|
91
|
+
ruby
|
|
92
|
+
x86_64-linux
|
|
93
|
+
|
|
94
|
+
DEPENDENCIES
|
|
95
|
+
lex-microsoft_teams!
|
|
96
|
+
rake
|
|
97
|
+
rspec
|
|
98
|
+
rspec_junit_formatter
|
|
99
|
+
rubocop
|
|
100
|
+
simplecov
|
|
101
|
+
|
|
102
|
+
BUNDLED WITH
|
|
103
|
+
2.6.9
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Esity
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|