426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
216 lines
17 KiB
Markdown
216 lines
17 KiB
Markdown
# @elizaos/plugin-x
|
|
|
|
X (formerly Twitter) connector for elizaOS agents: posting, mentions, replies, DMs, timeline actions, and autonomous content discovery.
|
|
|
|
## Purpose / role
|
|
|
|
Adds an `XService` to the elizaOS agent runtime that bridges the agent to X/Twitter via Twitter API v2. The plugin registers as a message connector (DMs) and post connector (public tweets), starts autonomous polling loops for mentions/interactions/timeline/discovery, and wires OAuth 1.0a env-var credentials or OAuth 2.0 PKCE into the connector account manager.
|
|
|
|
Auto-enabled when `config.connectors.x` (or legacy `config.connectors.twitter`) is present and not explicitly disabled. Entry-point check lives in `auto-enable.ts`.
|
|
|
|
## Plugin surface
|
|
|
|
**Services** (registered in `XPlugin.services`):
|
|
|
|
- `XService` (`serviceType = "x"`) — Core service. Starts `TwitterClientInstance` per account; registers the X message connector (DMs) and post connector (public feed) with the runtime; manages per-account client lifecycle.
|
|
- `XWorkflowCredentialProvider` (`serviceType = "workflow_credential_provider"`) — Supplies OAuth 1.0a credentials (`twitterApi` credential type) to the workflow plugin. Only supports `twitterApi`; does not support `twitterOAuth2Api`.
|
|
|
|
**Providers** (registered in `XPlugin.providers`):
|
|
|
|
- `xIdentityProvider` (`name = "TWITTER_IDENTITY"`) — Makes the agent aware of its own X account: `@username`, screen name (display name), bio, and any configured nicknames. Reads the already-loaded `client.profile` via `XService.getActiveProfile()`; never issues a network call and returns empty context until the X client has authenticated. Nicknames are sourced from the `TWITTER_NICKNAMES` setting plus the character `name`.
|
|
|
|
**No actions or evaluators** are registered.
|
|
|
|
**Connectors registered at runtime startup** (inside `XService.start`):
|
|
|
|
- Message connector `"x"` — DM channel; implements `resolveTargets`, `listRecentTargets`, `getUserContext`, `fetchMessages`, `sendHandler`.
|
|
- Post connector `"x"` — Public feed; implements `postHandler`, `fetchFeed`, `searchPosts`.
|
|
|
|
**Auto-enable module**: `auto-enable.ts` — `shouldEnable(ctx)` returns `true` when `ctx.config.connectors.x` or `.twitter` block is present and not `{enabled: false}`.
|
|
|
|
## Layout
|
|
|
|
```
|
|
plugins/plugin-x/
|
|
auto-enable.ts Auto-enable entry-point (no heavy imports)
|
|
src/
|
|
index.ts XPlugin export; services: [XService, XWorkflowCredentialProvider]
|
|
base.ts ClientBase — wraps the twitter-api-v2 client; caches profile, fetches timeline/tweets/search
|
|
environment.ts twitterEnvSchema (zod); TwitterConfig type; validateTwitterConfig()
|
|
types.ts TwitterClientState, ITwitterClient, event payload types, Tweet, MediaData
|
|
constants.ts Shared string constants
|
|
templates.ts LLM prompt templates for post/interaction generation
|
|
post.ts TwitterPostClient — autonomous tweet generation loop
|
|
interactions.ts TwitterInteractionClient — mention/reply polling loop; search-discovered target-user/timeline engagement (like/retweet/quote/reply)
|
|
timeline.ts TwitterTimelineClient — home/following feed action loop (like/retweet/quote/reply); interprets tweet media (image/gif/video) via IMAGE_DESCRIPTION before deciding/replying
|
|
discovery.ts TwitterDiscoveryClient — autonomous follow/like/reply discovery loop
|
|
lifeops-message-adapter.ts LifeOps BaseMessageAdapter adapter — bridges XService DM send/list to the LifeOps message-adapter interface
|
|
identity-provider.ts xIdentityProvider (TWITTER_IDENTITY) — surfaces the agent's own username/screen name/bio/nicknames into prompt context
|
|
connector-account-provider.ts ConnectorAccountProvider impl; bridges env-mode + OAuth PKCE to ConnectorAccountManager
|
|
connector-credential-refs.ts Persists connector credential references into runtime cache
|
|
workflow-credential-provider.ts XWorkflowCredentialProvider service
|
|
client/
|
|
index.ts Re-exports Client, SearchMode, QueryTweetsResponse, Tweet
|
|
client.ts Low-level twitter-api-v2 wrapper (Client class)
|
|
tweets.ts Tweet/Mention types; tweet fetch helpers
|
|
accounts.ts resolveTwitterAccountConfig, resolveDefaultXAccountId, resolveRequestedXAccountId
|
|
auth.ts TwitterAuth — authenticated twitter-api-v2 client wrapper
|
|
profile.ts getProfile, parseProfile, profile caching
|
|
search.ts searchTweets / searchProfiles / searchQuotedTweets generators
|
|
relationships.ts followUser, getFollowing, getFollowers helpers
|
|
errors.ts Typed error classes
|
|
api-types.ts Raw API response type shapes
|
|
auth-providers/
|
|
factory.ts createTwitterAuthProvider — picks env-mode vs PKCE
|
|
pkce.ts createCodeVerifier / createCodeChallenge helpers
|
|
env.ts Env-mode (OAuth 1.0a) auth provider
|
|
oauth2-pkce.ts OAuth 2.0 PKCE auth provider
|
|
interactive.ts Interactive authorization flow helpers
|
|
token-store.ts Token persistence helpers
|
|
types.ts Auth provider type definitions
|
|
services/
|
|
x.service.ts XService + TwitterClientInstance (orchestrates all sub-clients)
|
|
IPostService.ts IPostService interface; Post / CreatePostOptions / GetPostsOptions types
|
|
PostService.ts TwitterPostService — createPost / getPosts / getMentions via ClientBase
|
|
IMessageService.ts IMessageService interface; Message / SendMessageOptions types
|
|
MessageService.ts TwitterMessageService — DM send/list via ClientBase
|
|
utils.ts sendTweet, SentTweet, fetchMediaData, parseActionResponseFromText; re-exports utils/error-handler
|
|
utils/
|
|
settings.ts getSetting(runtime, key) — checks runtime settings then process.env
|
|
memory.ts createMemorySafe, ensureTwitterContext, isTweetProcessed, buildTwitterMessageMetadata
|
|
time.ts getEpochMs
|
|
error-handler.ts Shared API error handling helpers
|
|
```
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
bun run --cwd plugins/plugin-x build # tsup production build → dist/
|
|
bun run --cwd plugins/plugin-x dev # tsup --watch
|
|
bun run --cwd plugins/plugin-x test # vitest run
|
|
bun run --cwd plugins/plugin-x test:coverage # vitest run --coverage
|
|
bun run --cwd plugins/plugin-x lint # biome check --write --unsafe
|
|
bun run --cwd plugins/plugin-x lint:check # biome check (read-only)
|
|
bun run --cwd plugins/plugin-x format # biome format --write
|
|
bun run --cwd plugins/plugin-x format:check # biome format (read-only)
|
|
bun run --cwd plugins/plugin-x clean # rm -rf dist .turbo
|
|
```
|
|
|
|
## Config / env vars
|
|
|
|
All vars are read via `getSetting(runtime, key)` which checks `runtime.getSetting()` then `process.env`.
|
|
|
|
| Variable | Required | Default | Description |
|
|
|---|---|---|---|
|
|
| `TWITTER_AUTH_MODE` | No | `env` | `env` = OAuth 1.0a static credentials; `oauth` = OAuth 2.0 PKCE interactive |
|
|
| `TWITTER_API_KEY` | env-mode | — | Consumer API key |
|
|
| `TWITTER_API_SECRET_KEY` | env-mode | — | Consumer API secret |
|
|
| `TWITTER_ACCESS_TOKEN` | env-mode | — | Access token (must have write permissions) |
|
|
| `TWITTER_ACCESS_TOKEN_SECRET` | env-mode | — | Access token secret |
|
|
| `TWITTER_CLIENT_ID` | oauth-mode | — | OAuth 2.0 Client ID |
|
|
| `TWITTER_REDIRECT_URI` | oauth-mode | — | OAuth 2.0 redirect URI (loopback recommended) |
|
|
| `TWITTER_SCOPES` | No | `tweet.read tweet.write users.read offline.access` | OAuth 2.0 scopes |
|
|
| `TWITTER_ACCOUNT_ID` | No | `""` | Account ID for the default X account when connector account routing is enabled |
|
|
| `TWITTER_DEFAULT_ACCOUNT_ID` | No | `default` | Default account ID for multi-account routing |
|
|
| `TWITTER_ACCOUNTS` | No | — | JSON blob of account-scoped credentials for multi-account pilots |
|
|
| `TWITTER_DRY_RUN` | No | `false` | Simulate all actions; nothing is actually posted |
|
|
| `TWITTER_ENABLE_POST` | No | `false` | Enable autonomous tweet generation loop |
|
|
| `TWITTER_ENABLE_REPLIES` | No | `true` | Enable mention/reply handling loop |
|
|
| `TWITTER_ENABLE_ACTIONS` | No | `false` | Enable timeline action loop (like/retweet/quote) |
|
|
| `TWITTER_ENABLE_DISCOVERY` | No | `false` | Enable discovery loop (follows + engagement) |
|
|
| `TWITTER_TARGET_USERS` | No | `""` | Comma-separated usernames to target; empty = all; `*` = all |
|
|
| `TWITTER_NICKNAMES` | No | `""` | Comma-separated nicknames/aliases the agent answers to; surfaced via the `TWITTER_IDENTITY` provider |
|
|
| `TWITTER_RETRY_LIMIT` | No | `5` | Max retries on failed operations |
|
|
| `TWITTER_POST_INTERVAL` | No | `120` | Fixed minutes between posts when MIN/MAX not set |
|
|
| `TWITTER_POST_INTERVAL_MIN` | No | `90` | Minimum minutes between posts |
|
|
| `TWITTER_POST_INTERVAL_MAX` | No | `180` | Maximum minutes between posts |
|
|
| `TWITTER_POST_IMMEDIATELY` | No | `false` | Skip first interval and post on startup |
|
|
| `TWITTER_ENGAGEMENT_INTERVAL` | No | `30` | Fixed minutes between engagements |
|
|
| `TWITTER_ENGAGEMENT_INTERVAL_MIN` | No | `20` | Minimum minutes between engagements |
|
|
| `TWITTER_ENGAGEMENT_INTERVAL_MAX` | No | `40` | Maximum minutes between engagements |
|
|
| `TWITTER_DISCOVERY_INTERVAL_MIN` | No | `15` | Minimum minutes between discovery cycles |
|
|
| `TWITTER_DISCOVERY_INTERVAL_MAX` | No | `30` | Maximum minutes between discovery cycles |
|
|
| `TWITTER_MAX_ENGAGEMENTS_PER_RUN` | No | `5` | Max interactions per engagement cycle |
|
|
| `TWITTER_MAX_TWEET_LENGTH` | No | `280` | Max tweet length |
|
|
| `TWITTER_MIN_FOLLOWER_COUNT` | No | `100` | Min follower count for discovery follows |
|
|
| `TWITTER_MAX_FOLLOWS_PER_CYCLE` | No | `5` | Max follows per discovery cycle |
|
|
| `TWITTER_AUTO_RESPOND_MENTIONS` | No | `true` | Auto-respond to mentions |
|
|
| `TWITTER_AUTO_RESPOND_REPLIES` | No | `true` | Auto-respond to replies |
|
|
| `TWITTER_TIMELINE_MODE` | No | `home` | Timeline mode |
|
|
|
|
## How to extend
|
|
|
|
**Add a new autonomous loop** (e.g., a scheduled quote-tweet cycle):
|
|
|
|
1. Create `src/my-feature.ts` exporting a class with `start()` and `stop()` methods. Follow `post.ts` or `discovery.ts` as a pattern — construct with `(client: ClientBase, runtime: IAgentRuntime, state: TwitterClientState)`.
|
|
2. Add a field to `TwitterClientInstance` in `src/services/x.service.ts` and instantiate it in the constructor when the relevant env var is set.
|
|
3. Call `await instance.myFeature.start()` inside `startAutonomousClients()` and `await client.myFeature.stop()` inside `XService.stop()`.
|
|
|
|
**Add a new IPostService or IMessageService method**:
|
|
|
|
1. Extend the interface in `src/services/IPostService.ts` or `src/services/IMessageService.ts`.
|
|
2. Implement in `src/services/PostService.ts` / `src/services/MessageService.ts`.
|
|
3. Call the new method from `XService` handler methods as needed.
|
|
|
|
**Add a new connector capability** (e.g., `search_posts` on the message connector):
|
|
|
|
1. Add the capability string to `X_CONNECTOR_CAPABILITIES` in `x.service.ts`.
|
|
2. Implement the handler method on `XService`.
|
|
3. Include it in the `XMessageConnectorRegistration` passed to `runtime.registerMessageConnector`.
|
|
|
|
## Conventions / gotchas
|
|
|
|
- **OAuth 1.0a (`env` mode)** is the default. It requires all four vars: `TWITTER_API_KEY`, `TWITTER_API_SECRET_KEY`, `TWITTER_ACCESS_TOKEN`, `TWITTER_ACCESS_TOKEN_SECRET`. The app must have "Read and write" permissions in the Twitter Developer Portal. After changing permissions, regenerate access tokens.
|
|
- **OAuth 2.0 PKCE (`oauth` mode)** requires `TWITTER_CLIENT_ID` and `TWITTER_REDIRECT_URI`. No client secret is stored. Tokens persist per `accountId` via the runtime cache (key `twitter/oauth2/tokens/<agentId>/<accountId>`) and the connector credential store — see `client/auth-providers/token-store.ts`. There is no local-file fallback; token persistence requires runtime cache APIs.
|
|
- **`TWITTER_ENABLE_POST=false` by default.** Posting is opt-in to prevent accidental bots.
|
|
- **`TWITTER_ENABLE_ACTIONS=false` by default.** Timeline actions (likes, retweets) are also opt-in.
|
|
- **Discovery auto-enables with actions.** `TWITTER_ENABLE_DISCOVERY` defaults to `true` when `TWITTER_ENABLE_ACTIONS=true`, unless explicitly set to `false`.
|
|
- **`TWITTER_DRY_RUN=true`** simulates all write operations without calling the API. Use during development.
|
|
- **`getSetting(runtime, key)`** in `src/utils/settings.ts` is the canonical way to read any config — it checks runtime settings before `process.env`. Never read `process.env.TWITTER_*` directly inside service code.
|
|
- **Multi-account**: use `TWITTER_ACCOUNTS` (JSON) or add accounts via the ConnectorAccountManager HTTP surface. All methods on `XService` accept an `accountId` parameter. The default account is `TWITTER_DEFAULT_ACCOUNT_ID` (default: `"default"`).
|
|
- **`XWorkflowCredentialProvider`** only resolves `twitterApi` (OAuth 1.0a). Attempting to use `twitterOAuth2Api` with env-mode credentials will silently fail at workflow execution time.
|
|
- **twitter-api-v2** is the sole external Twitter API dep. Check its types and docs when adding new API calls.
|
|
- **One provider** (`TWITTER_IDENTITY`) is registered to make the agent aware of its own X identity; no actions or evaluators are registered. All other agent-facing behavior goes through message/post connector handlers.
|
|
|
|
<!-- BEGIN: evidence-and-e2e-mandate (managed; canonical standard = repo-root AGENTS.md) -->
|
|
## ⛔ NON-NEGOTIABLE — evidence, trajectories & real end-to-end tests
|
|
|
|
> The binding, repo-wide standard is **[AGENTS.md](../../AGENTS.md)**. Read it.
|
|
> Nothing in this package is *done* until it is *proven* done — a reviewer must confirm it
|
|
> works **without reading the code**, from the artifacts you attach. This applies to **every**
|
|
> feature, fix, refactor, and chore here. "Tests pass" is not proof; "CI is green" is not proof.
|
|
|
|
- **Record AND read model trajectories.** Capture the *actual* inputs and outputs of the model
|
|
from a **live** LLM — not the deterministic proxy, not a mock: the prompt, the
|
|
providers/context, the raw model output, every tool/action call, and the result. Then **open
|
|
the trajectory and review it by hand.** A captured-but-unread trajectory is not evidence
|
|
(`packages/scenario-runner/bin/eliza-scenarios run <scenario> --report <out>`).
|
|
- **Real, full-featured E2E — no larp.** Every feature ships detailed end-to-end tests that
|
|
drive the *real* path end to end. Not the happy "front door" only: cover error paths,
|
|
edge/empty/invalid input, concurrency, roles/permissions, and adversarial input. A test that
|
|
asserts against a mock/stub/fixture standing in for the thing under test **does not count**.
|
|
If the real model/device/chain/connector/account is hard to reach, **make it reachable — that
|
|
is the work**, not an excuse to mock. If the existing tests here are shallow or mocked, fixing
|
|
them is part of your change.
|
|
- **Screenshots + logs at every phase**, plus a **complete walkthrough video/run-through** of
|
|
the entire feature or view, start to finish (`bun run test:e2e:record`).
|
|
- **Manually review every artifact the change touches** — never just the green check: client
|
|
logs (console + network), server logs (`[ClassName] …`), the model trajectories in and out,
|
|
before/after full-page screenshots, **and the domain artifacts listed below for this package.**
|
|
- **No residuals. No shortcuts.** The goal is not "done" — it is *everything* done. Clear every
|
|
blocker by the **hard path**: build the real architecture, stand up the real
|
|
model/device/service, actually test it. Never leave a TODO, a stub, a stepping-stone, or a
|
|
"follow-up." When unsure, research thoroughly, weigh the options, and ship the best,
|
|
highest-effort, production-ready version. Keep going until every possibility is exhausted.
|
|
|
|
Artifacts → attached inline in the PR (MP4 video, JPG screenshots, logs in `<details>`); attach each evidence type **or**
|
|
explicitly mark it N/A with a reason — never leave it blank. If `develop` moved and changed
|
|
behavior, **re-capture** evidence; stale proof is worse than none.
|
|
|
|
**Capture & manually review for this package — platform connector:**
|
|
- A real (or sandbox-account) round-trip on the platform: inbound message → agent → outbound reply, captured as logs **and** a screenshot/recording of the actual conversation.
|
|
- The raw inbound event/webhook payload and the outbound API request/response, with IDs mapped correctly (`stringToUuid` / `createUniqueUuid`).
|
|
- Attachments, threads/replies, edits, multi-account, and rate-limit/error paths — not just a single text ping.
|
|
- The agent trajectory for the turn the connector drove.
|
|
<!-- END: evidence-and-e2e-mandate -->
|