opencode-microsoft-teams
v0.1.0
Published
An Effect-native Microsoft Graph polling plugin for OpenCode 2
Readme
OpenCode Microsoft Teams
An Effect-native OpenCode plugin that polls selected Microsoft Teams chats through Microsoft Graph using the signed-in user's delegated permissions. It reads new messages, decides whether help would be useful, and runs the configured OpenCode agent when needed. Jira, GitHub, and other actions use the agent's existing tools and MCP connections.
The package uses Bun 1.4.2, Biome, TypeScript 7's native preview compiler, Effect 4, and @opencode/[email protected] through @opencode/plugin/effect. Effect and its platform packages are pinned to 4.0.0-rc.112; upgrade those packages together. The published bundle includes the Bun platform adapter while Effect and the OpenCode API remain shared peer dependencies.
Configure Microsoft Graph
Follow Graph setup to register a single-tenant public client, grant delegated Chat.Read and ChatMessage.Send, sign in, and find the exact chat IDs to allow. The plugin does not use a client secret.
Set these variables in the environment of the OpenCode process:
GRAPH_CLIENT_ID=your-application-client-id
GRAPH_TENANT_ID=your-tenant-id
GRAPH_TOKEN_CACHE=/absolute/path/to/graph-token-cache.jsonThe cache contains sensitive delegated authentication data in private plaintext. Keep it on an encrypted volume and restrict its file permissions. GRAPH_TOKEN_CACHE must be an absolute path.
Run the initial device login from the checkout:
bun run loginFor the published package, use:
bunx --bun --package opencode-microsoft-teams opencode-teams-auth loginComplete login before starting OpenCode. The plugin silently refreshes the delegated token from the local MSAL cache while it runs. If the refresh fails, run the login command again.
Plugin configuration
After the package is published, add it to opencode.jsonc:
{
"plugins": [
{
"package": "[email protected]",
"options": {
"chats": [
{ "id": "19:[email protected]" },
{
"id": "19:[email protected]",
"agent": "reviewer",
"prompt": "Help this team review pull requests. Keep replies concise."
}
],
"confidence": 0.85,
"cooldownMs": 60000
}
}
]
}chats is an exact allowlist. Only those Graph chat IDs are polled. The signed-in user's own messages are ignored so the plugin does not process its own replies. An omitted agent or model uses OpenCode's defaults. Configure named agents, model credentials, Jira/GitHub tools, and tool permissions in OpenCode before selecting them here. Permission requests are handled in OpenCode; this plugin does not add Teams controls.
See docs/opencode-config.example.jsonc for a copyable configuration example.
Behavior
Each configured chat is polled independently. The first poll starts at the current time by default, so existing history is not processed. On restart, the plugin uses persisted message timestamps and IDs to catch up across Graph pagination. A poll reads up to the configured history limit for the agent context.
In ambient mode, a tool-free model call evaluates recent conversation context. Low-confidence decisions, ordinary conversation, and malformed model output stay silent. In mentions mode, the plugin considers messages that mention the signed-in user. A qualifying request runs the selected OpenCode agent, and the final reply is sent as the signed-in user with the configured replyPrefix (default [OpenCode] ). The agent can return [SILENT] to suppress a reply. Replies are capped at 6,000 characters.
Messages are processed sequentially within each chat, while different chats can run concurrently. cooldownMs limits ambient replies; mention-triggered messages bypass the ambient cooldown. timeoutMs interrupts agent work that exceeds its limit.
| Option | Default | Purpose |
| --- | --- | --- |
| chats | Required | Nonempty list of exact { "id": "..." } Graph chat IDs |
| agent | OpenCode default | Agent ID; also available per chat |
| model | OpenCode default | provider/model or provider/model#variant; also per chat |
| triageModel | OpenCode default | Model for tool-free triage; also per chat |
| prompt | Built-in colleague prompt | Replaces the task prompt; also per chat |
| triagePrompt | Built-in conservative policy | Decision prompt; also per chat |
| mode | ambient | ambient or mentions; also per chat |
| confidence | 0.85 | Minimum confidence for ambient responses; also per chat |
| cooldownMs | 60000 | Minimum gap between ambient replies; also per chat |
| pollIntervalMs | 30000 | Delay between Graph polls |
| initialLookbackMs | 0 | How far before startup the first poll may read |
| replyPrefix | [OpenCode] | Prefix added to automated replies |
| historyLimit | 12 | Recent messages supplied as agent context |
| timeoutMs | 180000 | Maximum agent execution time |
pollIntervalMs accepts 1,000–3,600,000 milliseconds. initialLookbackMs accepts up to seven days; configure it when the first run should process a recent window, or keep it at 0 to start at the current time. historyLimit accepts up to 100 messages. mentions mode means messages that mention the signed-in user; it does not require a separate identity or installation.
State and lifecycle
OpenCode plugin storage holds per-tenant, per-user, per-chat message timestamps and IDs, the OpenCode session ID, recent transcript state, and the last reply timestamp. Treat that storage as chat data. On first activation, the plugin persists a starting cursor at the current time or the configured lookback boundary. Later polls catch up across Graph pages using that cursor. Only newly created messages advance processing; edits, reactions, and deletions do not retrigger work. A message is claimed and persisted before tool work begins, which avoids repeating writes after a restart. Failed work, including a failed Graph send, is not automatically retried; inspect the OpenCode session and logs before asking for another attempt. Persistence reduces duplicate processing but does not provide an exactly-once guarantee for external actions.
The plugin's Effect scope owns the Graph client, token refresh boundary, polling workers, and OpenCode work. Reloading or unloading cancels the workers and interrupts active agent work. Use one plugin instance per OpenCode workspace.
Headless startup
OpenCode loads workspace plugins lazily. Follow headless startup to activate the workspace after starting opencode2 serve, then inspect the OpenCode logs for plugin activation and polling messages.
Develop
bun install --frozen-lockfile
bun run check
bun run typecheck
bun test
bun run buildTo load this checkout before publication, run bun run build, then use the absolute path to this repository directory as the plugin's package value. OpenCode's local plugin discovery uses the root index.js, which forwards to the build. Set the Graph variables in the process that starts OpenCode. Rebuild after editing and reload the plugin through OpenCode to apply changes.
Tests cover triage, chat isolation, message claims, pagination, restart catch-up, session interaction, and scoped shutdown without real Graph credentials. A live Microsoft Graph account and a configured OpenCode model are needed for a full integration test.
The GitHub workflows run validation and publish public releases using npm OIDC. See publishing setup for initial package registration and trusted publisher configuration.
