Files
openclaw/docs/channels/tlon.md
Hunter Miller f4682742d9 feat: update tlon channel/plugin to be more fully featured (#21208)
* feat(tlon): sync with openclaw-tlon master

- Add tlon CLI tool registration with binary lookup
- Add approval, media, settings, foreigns, story, upload modules
- Add http-api wrapper for Urbit connection patching
- Update types for defaultAuthorizedShips support
- Fix type compatibility with core plugin SDK
- Stub uploadFile (API not yet available in @tloncorp/api-beta)
- Remove incompatible test files (security, sse-client, upload)

* chore(tlon): remove dead code

Remove unused Urbit channel client files:
- channel-client.ts
- channel-ops.ts
- context.ts

These were not imported anywhere in the extension.

* feat(tlon): add image upload support via @tloncorp/api

- Import configureClient and uploadFile from @tloncorp/api
- Implement uploadImageFromUrl using uploadFile
- Configure API client before media uploads
- Update dependency to github:tloncorp/api-beta#main

* fix(tlon): restore SSRF protection with event ack tracking

- Restore context.ts and channel-ops.ts for SSRF support
- Restore sse-client.ts with urbitFetch for SSRF-protected requests
- Add event ack tracking from openclaw-tlon (acks every 20 events)
- Pass ssrfPolicy through authenticate() and UrbitSSEClient
- Fixes security regression from sync with openclaw-tlon

* fix(tlon): restore buildTlonAccountFields for allowPrivateNetwork

The inlined payload building was missing allowPrivateNetwork field,
which would prevent the setting from being persisted to config.

* fix(tlon): restore SSRF protection in probeAccount

- Restore channel-client.ts for UrbitChannelClient
- Use UrbitChannelClient with ssrfPolicy in probeAccount
- Ensures account probe respects allowPrivateNetwork setting

* feat(tlon): add ownerShip to setup flow

ownerShip should always be set as it controls who receives
approval requests and can approve/deny actions.

* chore(tlon): remove unused http-api.ts

After restoring SSRF protection, probeAccount uses UrbitChannelClient
instead of @urbit/http-api. The http-api.ts wrapper is no longer needed.

* refactor(tlon): simplify probeAccount to direct /~/name request

No channel needed - just authenticate and GET /~/name.
Removes UrbitChannelClient, keeping only UrbitSSEClient for monitor.

* chore(tlon): add logging for event acks

* chore(tlon): lower ack threshold to 5 for testing

* fix(tlon): address security review issues

- Fix SSRF in upload.ts: use urbitFetch with SSRF protection
- Fix SSRF in media.ts: use urbitFetch with SSRF protection
- Add command whitelist to tlon tool to prevent command injection
- Add getDefaultSsrFPolicy() helper for uploads/downloads

* fix(tlon): restore auth retry and add reauth on SSE reconnect

- Add authenticateWithRetry() helper with exponential backoff (restores lost logic from #39)
- Add onReconnect callback to re-authenticate when SSE stream reconnects
- Add UrbitSSEClient.updateCookie() method for proper cookie normalization on reauth

* fix(tlon): add infinite reconnect with reset after max attempts

Instead of giving up after maxReconnectAttempts, wait 10 seconds then
reset the counter and keep trying. This ensures the monitor never
permanently disconnects due to temporary network issues.

* test(tlon): restore security, sse-client, and upload tests

- security.test.ts: DM allowlist, group invite, bot mention detection, ship normalization
- sse-client.test.ts: subscription handling, cookie updates, reconnection params
- upload.test.ts: image upload with SSRF protection, error handling

* fix(tlon): restore DM partner ship extraction for proper routing

- Add extractDmPartnerShip() to extract partner from 'whom' field
- Use partner ship for routing (more reliable than essay.author)
- Explicitly ignore bot's own outbound DM events
- Log mismatch between author and partner for debugging

* chore(tlon): restore ack threshold to 20

* chore(tlon): sync slash commands support from upstream

- Add stripBotMention for proper CommandBody parsing
- Add command authorization logic for owner-only slash commands
- Add CommandAuthorized and CommandSource to context payload

* fix(tlon): resolve TypeScript errors in tests and monitor

- Store validated account url/code before closure to fix type narrowing
- Fix test type annotations for mode rules
- Add proper Response type cast in sse-client mock
- Use optional chaining for init properties

* docs(tlon): update docs for new config options and capabilities

- Document ownerShip for approval system
- Document autoAcceptDmInvites and autoAcceptGroupInvites
- Update status to reflect rich text and image support
- Add bundled skill section
- Update notes with formatting and image details
- Fix pnpm-lock.yaml conflict

* docs(tlon): fix dmAllowlist description and improve allowPrivateNetwork docs

- Correct dmAllowlist: empty means no DMs allowed (not allow all)
- Promote allowPrivateNetwork to its own section with examples
- Add warning about SSRF protection implications

* docs(tlon): clarify ownerShip is auto-authorized everywhere

- Add ownerShip to minimal config example (recommended)
- Document that owner is automatically allowed for DMs and channels
- No need to add owner to dmAllowlist or defaultAuthorizedShips

* docs(tlon): add capabilities table, troubleshooting, and config reference

Align with Matrix docs format:
- Capabilities table for quick feature reference
- Troubleshooting section with common failures
- Configuration reference with all options

* docs(tlon): fix reactions status and expand bundled skill section

- Reactions ARE supported via bundled skill (not missing)
- Add link to skill GitHub repo
- List skill capabilities: contacts, channels, groups, DMs, reactions, settings

* fix(tlon): use crypto.randomUUID instead of Math.random for channel ID

Fixes security test failure - Math.random is flagged as weak randomness.

* docs: fix markdown lint - add blank line before </Step>

* fix: address PR review issues for tlon plugin

- upload.ts: Use fetchWithSsrFGuard directly instead of urbitFetch to
  preserve full URL path when fetching external images; add release() call
- media.ts: Same fix - use fetchWithSsrFGuard for external media downloads;
  add release() call to clean up resources
- channel.ts: Use urbitFetch for poke API to maintain consistent SSRF
  protection (DNS pinning + redirect handling)
- upload.test.ts: Update mocks to use fetchWithSsrFGuard instead of urbitFetch

Addresses blocking issues from jalehman's review:
1. Fixed incorrect URL being fetched (validateUrbitBaseUrl was stripping path)
2. Fixed missing release() calls that could leak resources
3. Restored guarded fetch semantics for poke operations

* docs: add tlon changelog fragment

* style: format tlon monitor

* fix: align tlon lockfile and sse id generation

* docs: fix onboarding markdown list spacing

---------

Co-authored-by: Josh Lehman <josh@martian.engineering>
2026-03-02 16:23:42 -08:00

7.0 KiB

summary, read_when, title
summary read_when title
Tlon/Urbit support status, capabilities, and configuration
Working on Tlon/Urbit channel features
Tlon

Tlon (plugin)

Tlon is a decentralized messenger built on Urbit. OpenClaw connects to your Urbit ship and can respond to DMs and group chat messages. Group replies require an @ mention by default and can be further restricted via allowlists.

Status: supported via plugin. DMs, group mentions, thread replies, rich text formatting, and image uploads are supported. Reactions and polls are not yet supported.

Plugin required

Tlon ships as a plugin and is not bundled with the core install.

Install via CLI (npm registry):

openclaw plugins install @openclaw/tlon

Local checkout (when running from a git repo):

openclaw plugins install ./extensions/tlon

Details: Plugins

Setup

  1. Install the Tlon plugin.
  2. Gather your ship URL and login code.
  3. Configure channels.tlon.
  4. Restart the gateway.
  5. DM the bot or mention it in a group channel.

Minimal config (single account):

{
  channels: {
    tlon: {
      enabled: true,
      ship: "~sampel-palnet",
      url: "https://your-ship-host",
      code: "lidlut-tabwed-pillex-ridrup",
      ownerShip: "~your-main-ship", // recommended: your ship, always allowed
    },
  },
}

Private/LAN ships

By default, OpenClaw blocks private/internal hostnames and IP ranges for SSRF protection. If your ship is running on a private network (localhost, LAN IP, or internal hostname), you must explicitly opt in:

{
  channels: {
    tlon: {
      url: "http://localhost:8080",
      allowPrivateNetwork: true,
    },
  },
}

This applies to URLs like:

  • http://localhost:8080
  • http://192.168.x.x:8080
  • http://my-ship.local:8080

⚠️ Only enable this if you trust your local network. This setting disables SSRF protections for requests to your ship URL.

Group channels

Auto-discovery is enabled by default. You can also pin channels manually:

{
  channels: {
    tlon: {
      groupChannels: ["chat/~host-ship/general", "chat/~host-ship/support"],
    },
  },
}

Disable auto-discovery:

{
  channels: {
    tlon: {
      autoDiscoverChannels: false,
    },
  },
}

Access control

DM allowlist (empty = no DMs allowed, use ownerShip for approval flow):

{
  channels: {
    tlon: {
      dmAllowlist: ["~zod", "~nec"],
    },
  },
}

Group authorization (restricted by default):

{
  channels: {
    tlon: {
      defaultAuthorizedShips: ["~zod"],
      authorization: {
        channelRules: {
          "chat/~host-ship/general": {
            mode: "restricted",
            allowedShips: ["~zod", "~nec"],
          },
          "chat/~host-ship/announcements": {
            mode: "open",
          },
        },
      },
    },
  },
}

Owner and approval system

Set an owner ship to receive approval requests when unauthorized users try to interact:

{
  channels: {
    tlon: {
      ownerShip: "~your-main-ship",
    },
  },
}

The owner ship is automatically authorized everywhere — DM invites are auto-accepted and channel messages are always allowed. You don't need to add the owner to dmAllowlist or defaultAuthorizedShips.

When set, the owner receives DM notifications for:

  • DM requests from ships not in the allowlist
  • Mentions in channels without authorization
  • Group invite requests

Auto-accept settings

Auto-accept DM invites (for ships in dmAllowlist):

{
  channels: {
    tlon: {
      autoAcceptDmInvites: true,
    },
  },
}

Auto-accept group invites:

{
  channels: {
    tlon: {
      autoAcceptGroupInvites: true,
    },
  },
}

Delivery targets (CLI/cron)

Use these with openclaw message send or cron delivery:

  • DM: ~sampel-palnet or dm/~sampel-palnet
  • Group: chat/~host-ship/channel or group:~host-ship/channel

Bundled skill

The Tlon plugin includes a bundled skill (@tloncorp/tlon-skill) that provides CLI access to Tlon operations:

  • Contacts: get/update profiles, list contacts
  • Channels: list, create, post messages, fetch history
  • Groups: list, create, manage members
  • DMs: send messages, react to messages
  • Reactions: add/remove emoji reactions to posts and DMs
  • Settings: manage plugin permissions via slash commands

The skill is automatically available when the plugin is installed.

Capabilities

Feature Status
Direct messages Supported
Groups/channels Supported (mention-gated by default)
Threads Supported (auto-replies in thread)
Rich text Markdown converted to Tlon format
Images Uploaded to Tlon storage
Reactions Via bundled skill
Polls Not yet supported
Native commands Supported (owner-only by default)

Troubleshooting

Run this ladder first:

openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor

Common failures:

  • DMs ignored: sender not in dmAllowlist and no ownerShip configured for approval flow.
  • Group messages ignored: channel not discovered or sender not authorized.
  • Connection errors: check ship URL is reachable; enable allowPrivateNetwork for local ships.
  • Auth errors: verify login code is current (codes rotate).

Configuration reference

Full configuration: Configuration

Provider options:

  • channels.tlon.enabled: enable/disable channel startup.
  • channels.tlon.ship: bot's Urbit ship name (e.g. ~sampel-palnet).
  • channels.tlon.url: ship URL (e.g. https://sampel-palnet.tlon.network).
  • channels.tlon.code: ship login code.
  • channels.tlon.allowPrivateNetwork: allow localhost/LAN URLs (SSRF bypass).
  • channels.tlon.ownerShip: owner ship for approval system (always authorized).
  • channels.tlon.dmAllowlist: ships allowed to DM (empty = none).
  • channels.tlon.autoAcceptDmInvites: auto-accept DMs from allowlisted ships.
  • channels.tlon.autoAcceptGroupInvites: auto-accept all group invites.
  • channels.tlon.autoDiscoverChannels: auto-discover group channels (default: true).
  • channels.tlon.groupChannels: manually pinned channel nests.
  • channels.tlon.defaultAuthorizedShips: ships authorized for all channels.
  • channels.tlon.authorization.channelRules: per-channel auth rules.
  • channels.tlon.showModelSignature: append model name to messages.

Notes

  • Group replies require a mention (e.g. ~your-bot-ship) to respond.
  • Thread replies: if the inbound message is in a thread, OpenClaw replies in-thread.
  • Rich text: Markdown formatting (bold, italic, code, headers, lists) is converted to Tlon's native format.
  • Images: URLs are uploaded to Tlon storage and embedded as image blocks.