Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Changelog

All notable changes to SocialAgent are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
adheres to [Semantic Versioning](https://semver.org/).

## [1.4.0] - 2026-05-04

### Added
- **Threads provider** (`SocialAgent.Providers.Threads`) implementing
`ISocialMediaProvider` against Meta's Threads API. Maps `/v1.0/me`,
`/v1.0/me/threads`, `/v1.0/me/mentions`, and `/v1.0/me/replies` to the
standard provider methods. Notifications are synthesized from mentions
plus replies.
- **Threads long-lived token refresh** via the new
`ThreadsTokenRefreshService`. Calls
`GET /refresh_access_token?grant_type=th_refresh_token` ahead of the
configured `RefreshThresholdDays` (default 7) and persists the new token
to the database so refreshes survive pod restarts.
- **`ProviderToken` entity** and repository methods
(`GetProviderTokenAsync`, `UpsertProviderTokenAsync`) for storing
rotating OAuth bearer tokens.
- New configuration section `SocialAgent:Providers:Threads` (`Enabled`,
`BaseUrl`, `AccessToken`, `IncludePostInsights`, `RefreshThresholdDays`,
`RefreshCheckIntervalHours`).
- Kubernetes ConfigMap / Secret / Deployment wiring for the Threads
provider.
- `docs/providers/threads.md` long-form provider documentation covering
OAuth scopes, token refresh, and known limitations.

### Changed
- Agent version bumped from **1.3.4** to **1.4.0**; the `/.well-known/agent-card.json`
now reports `1.4.0`.
- Agent card description updated to mention Threads alongside Mastodon
and Bluesky.

### Migration notes
- The new `ProviderTokens` table is created automatically by
`DatabaseMigrationService` on every startup, both for fresh databases
(via `EnsureCreatedAsync`) and existing PostgreSQL/SQLite databases
(via an idempotent `CREATE TABLE IF NOT EXISTS` patch). No manual DDL
is required when rolling out 1.4.0. See
[`docs/providers/threads.md`](docs/providers/threads.md#database-schema-change)
for the exact DDL the host runs.
- The `threads_manage_replies` and `threads_manage_insights` OAuth scopes
require Meta App Review for production tokens. The provider degrades
gracefully when scopes are missing: affected endpoints log a warning and
return empty.

## Prior versions

For commits prior to 1.4.0, see `git log`. Notable changes include the
A2A 1.0 migration (#4), data retention service (#3), API key
authentication (#2), and the initial scaffold.
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ SocialAgent is an **A2A-enabled social media monitoring agent** built on .NET 10
- **`src/SocialAgent.Analytics/`** — Analytics engine computing engagement summaries, top posts, follower insights, platform comparisons
- **`src/SocialAgent.Providers.Mastodon/`** — Mastodon REST API client implementing `ISocialMediaProvider`
- **`src/SocialAgent.Providers.Bluesky/`** — Bluesky AT Protocol client implementing `ISocialMediaProvider`
- **`src/SocialAgent.Host/`** — ASP.NET Core host, A2A request handler (`SocialAgentA2AHandler` implementing `A2A.IAgentHandler`), skill dispatcher (`SkillDispatcher`), skill metadata (`SkillCatalog`), stub `AIAgent` (`SocialAgentStubAgent`, framework-required name carrier), background polling service
- **`src/SocialAgent.Providers.Threads/`** — Threads Graph API v1.0 client implementing `ISocialMediaProvider`. Adds `ThreadsTokenStore` (in-memory current token + expiry) and a `RefreshTokenAsync` method on the provider; the host owns the refresh schedule via `ThreadsTokenRefreshService`.
- **`src/SocialAgent.Host/`** — ASP.NET Core host, A2A request handler (`SocialAgentA2AHandler` implementing `A2A.IAgentHandler`), skill dispatcher (`SkillDispatcher`), skill metadata (`SkillCatalog`), stub `AIAgent` (`SocialAgentStubAgent`, framework-required name carrier), background polling service, and `ThreadsTokenRefreshService` (registered only when Threads is enabled — seeds the token from DB on startup, refreshes ahead of `RefreshThresholdDays`, persists via `ISocialDataRepository`)
- **`tests/`** — MSTest unit tests with NSubstitute for mocking
- **`deploy/k8s/`** — Kubernetes manifests (Deployment, Service, ConfigMap, Secret)

Expand Down Expand Up @@ -62,7 +63,7 @@ Each provider implements `ISocialMediaProvider` and is registered via DI extensi
- **Async-first** — all I/O is Task-based
- **NSubstitute** is the mocking framework for tests
- **Configuration** — standard .NET config stack: `appsettings.json`, environment variables, user secrets, k8s Secrets
- **Database** — EF Core with SQLite for dev, PostgreSQL for prod. `DatabaseMigrationService` runs `EnsureCreatedAsync` on startup.
- **Database** — EF Core with SQLite for dev, PostgreSQL for prod. `DatabaseMigrationService` runs `EnsureCreatedAsync` plus dialect-aware `CREATE TABLE IF NOT EXISTS` patches for tables added after the initial deployment (e.g. `ProviderTokens` in 1.4.0). When adding a new table, extend `DatabaseMigrationService.EnsureProviderTokensTableAsync`-style patch logic so existing live databases pick up the change. This is an interim approach until proper EF Core migrations are adopted.

### Future Integration

Expand Down
45 changes: 28 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ SocialAgent monitors your social media accounts across multiple platforms, colle
|---|---|---|
| Mastodon | ✅ Implemented | REST API v1 |
| Bluesky | ✅ Implemented | AT Protocol |
| Threads | ✅ Implemented | Threads Graph API v1.0 (with auto token refresh) |

### A2A Skills

Expand Down Expand Up @@ -73,8 +74,16 @@ dotnet user-secrets set "SocialAgent:Providers:Mastodon:AccessToken" "your-token
dotnet user-secrets set "SocialAgent:Providers:Bluesky:Enabled" "true"
dotnet user-secrets set "SocialAgent:Providers:Bluesky:Handle" "you.bsky.social"
dotnet user-secrets set "SocialAgent:Providers:Bluesky:AppPassword" "your-app-password"

# Set Threads credentials (long-lived token; auto-refreshes ahead of expiry)
dotnet user-secrets set "SocialAgent:Providers:Threads:Enabled" "true"
dotnet user-secrets set "SocialAgent:Providers:Threads:AccessToken" "your-long-lived-token"
```

For Threads-specific setup (OAuth scopes, Meta App Review,
`IncludePostInsights` tradeoffs, token-refresh behavior), see
[`docs/providers/threads.md`](docs/providers/threads.md).

### Database

- **Development:** SQLite (default, zero config)
Expand All @@ -96,23 +105,25 @@ The agent runs as a continuous Deployment (not CronJob) for A2A responsiveness.
## Architecture

```
┌─────────────────────────────────────────────────────────┐
│ SocialAgent.Host (ASP.NET Core) │
│ │
│ ┌──────────────┐ ┌─────────────────────────────────┐ │
│ │ A2A 1.0 │ │ Background Polling Services │ │
│ │ (MS Agent │ │ ┌───────────┐ ┌──────────────┐ │ │
│ │ Framework + │ │ │ Mastodon │ │ Bluesky │ │ │
│ │ A2A SDK) │ │ │ Provider │ │ Provider │ │ │
│ └──────┬───────┘ │ └─────┬─────┘ └──────┬───────┘ │ │
│ │ └───────┼───────────────┼─────────┘ │
│ ┌──────▼───────────────────▼───────────────▼─────────┐ │
│ │ Core (Domain Models & Interfaces) │ │
│ └──────────────────────┬─────────────────────────────┘ │
│ ┌──────────────────────▼─────────────────────────────┐ │
│ │ Data (EF Core — PostgreSQL / SQLite) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────┐
│ SocialAgent.Host (ASP.NET Core) │
│ │
│ ┌──────────────┐ ┌──────────────────────────────────────────────┐ │
│ │ A2A 1.0 │ │ Background Services │ │
│ │ (MS Agent │ │ ┌───────────┐ ┌──────────┐ ┌──────────────┐ │ │
│ │ Framework + │ │ │ Mastodon │ │ Bluesky │ │ Threads │ │ │
│ │ A2A SDK) │ │ │ Provider │ │ Provider │ │ Provider │ │ │
│ └──────┬───────┘ │ └─────┬─────┘ └────┬─────┘ └──────┬───────┘ │ │
│ │ │ │ │ │ │ │
│ │ │ + ThreadsTokenRefreshService (auto refresh)│ │
│ │ └───────┼────────────┼──────────────┼─────────┘ │
│ ┌──────▼───────────────── ▼ ▼ ▼─────────┐ │
│ │ Core (Domain Models & Interfaces) │ │
│ └─────────────────────────────┬──────────────────────────────────┘ │
│ ┌─────────────────────────────▼──────────────────────────────────┐ │
│ │ Data (EF Core — PostgreSQL / SQLite) │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
```

## Adding a New Provider
Expand Down
2 changes: 2 additions & 0 deletions SocialAgent.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
<Project Path="src/SocialAgent.Analytics/SocialAgent.Analytics.csproj" />
<Project Path="src/SocialAgent.Providers.Mastodon/SocialAgent.Providers.Mastodon.csproj" />
<Project Path="src/SocialAgent.Providers.Bluesky/SocialAgent.Providers.Bluesky.csproj" />
<Project Path="src/SocialAgent.Providers.Threads/SocialAgent.Providers.Threads.csproj" />
<Project Path="src/SocialAgent.Host/SocialAgent.Host.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/SocialAgent.Core.Tests/SocialAgent.Core.Tests.csproj" />
<Project Path="tests/SocialAgent.Analytics.Tests/SocialAgent.Analytics.Tests.csproj" />
<Project Path="tests/SocialAgent.Providers.Mastodon.Tests/SocialAgent.Providers.Mastodon.Tests.csproj" />
<Project Path="tests/SocialAgent.Providers.Bluesky.Tests/SocialAgent.Providers.Bluesky.Tests.csproj" />
<Project Path="tests/SocialAgent.Providers.Threads.Tests/SocialAgent.Providers.Threads.Tests.csproj" />
</Folder>
</Solution>
2 changes: 2 additions & 0 deletions deploy/k8s/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ data:
mastodon-instance-url: "https://fosstodon.org"
bluesky-enabled: "true"
bluesky-handle: "rocky.lhotka.net"
threads-enabled: "false"
threads-include-post-insights: "false"
public-base-url: "http://social-agent.rockbot.svc.cluster.local"
17 changes: 16 additions & 1 deletion deploy/k8s/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ spec:
spec:
containers:
- name: social-agent
image: rockylhotka/socialagent:1.3.4
image: rockylhotka/socialagent:1.4.0
ports:
- containerPort: 8080
env:
Expand Down Expand Up @@ -67,6 +67,21 @@ spec:
secretKeyRef:
name: social-agent-secrets
key: bluesky-app-password
- name: SocialAgent__Providers__Threads__Enabled
valueFrom:
configMapKeyRef:
name: social-agent-config
key: threads-enabled
- name: SocialAgent__Providers__Threads__IncludePostInsights
valueFrom:
configMapKeyRef:
name: social-agent-config
key: threads-include-post-insights
- name: SocialAgent__Providers__Threads__AccessToken
valueFrom:
secretKeyRef:
name: social-agent-secrets
key: threads-access-token
- name: Authentication__ApiKey
valueFrom:
secretKeyRef:
Expand Down
1 change: 1 addition & 0 deletions deploy/k8s/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ stringData:
connection-string: "Host=postgres;Database=socialagent;Username=socialagent;Password=CHANGE_ME"
mastodon-access-token: "CHANGE_ME"
bluesky-app-password: "CHANGE_ME"
threads-access-token: "CHANGE_ME"
api-key: "CHANGE_ME"
Loading
Loading