Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

17 KiB

@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.tsshouldEnable(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

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.

NON-NEGOTIABLE — evidence, trajectories & real end-to-end tests

The binding, repo-wide standard is 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.