← Back to Plugins
Channels

Photon

Mouxy By Mouxy 👁 82 views ▲ 0 votes

OpenClaw channel plugin for Photon/Spectrum iMessage

GitHub

Configuration Example

{
  "channels": {
    "photon": {
      "enabled": true,
      "provider": "imessage",
      "projectIdEnv": "PHOTON_PROJECT_ID",
      "projectSecretEnv": "PHOTON_PROJECT_SECRET",
      "dmPolicy": "pairing",
      "groupPolicy": "allowlist",
      "groupAllowFrom": [],
      "requireMention": true,
      "mentionNames": ["OpenClaw", "Assistant"],
      "sendReadReceipts": true,
      "typingIndicators": true,
      "inboundBatching": true,
      "inboundBatchDelayMs": 2500,
      "inboundBatchMaxDelayMs": 8000,
      "dispatchControlEvents": false,
      "dispatchPollVotes": true,
      "maxInboundAttachmentBytes": 20971520,
      "maxOutboundAttachmentBytes": 52428800,
      "nativeActions": true,
      "dangerousNativeActions": false,
      "effectAck": "confirmed",
      "miniAppDefaults": {
        "appName": "Example",
        "teamId": "TEAMID1234",
        "extensionBundleId": "com.example.messages.extension",
        "url": "https://example.com/imessage?id={{runId}}&phase={{phase}}",
        "caption": "Example",
        "subcaption": "Open in Messages",
        "summary": "Example mini-app card"
      }
    }
  }
}

README

# Photon OpenClaw Channel

Photon connects OpenClaw to Spectrum providers such as iMessage. It is built as
a normal OpenClaw channel plugin and routes inbound Spectrum messages through
OpenClaw's channel runtime.

## Minimal Config

```json
{
  "channels": {
    "photon": {
      "enabled": true,
      "provider": "imessage",
      "projectIdEnv": "PHOTON_PROJECT_ID",
      "projectSecretEnv": "PHOTON_PROJECT_SECRET",
      "dmPolicy": "pairing",
      "groupPolicy": "allowlist",
      "groupAllowFrom": [],
      "requireMention": true,
      "mentionNames": ["OpenClaw", "Assistant"],
      "sendReadReceipts": true,
      "typingIndicators": true,
      "inboundBatching": true,
      "inboundBatchDelayMs": 2500,
      "inboundBatchMaxDelayMs": 8000,
      "dispatchControlEvents": false,
      "dispatchPollVotes": true,
      "maxInboundAttachmentBytes": 20971520,
      "maxOutboundAttachmentBytes": 52428800,
      "nativeActions": true,
      "dangerousNativeActions": false,
      "effectAck": "confirmed",
      "miniAppDefaults": {
        "appName": "Example",
        "teamId": "TEAMID1234",
        "extensionBundleId": "com.example.messages.extension",
        "url": "https://example.com/imessage?id={{runId}}&phase={{phase}}",
        "caption": "Example",
        "subcaption": "Open in Messages",
        "summary": "Example mini-app card"
      }
    }
  }
}
```

## Production Config

The intended production profile is cloud iMessage through Photon/Spectrum:

```json
{
  "channels": {
    "photon": {
      "enabled": true,
      "provider": "imessage",
      "local": false,
      "projectIdEnv": "PHOTON_PROJECT_ID",
      "projectSecretEnv": "PHOTON_PROJECT_SECRET",
      "dmPolicy": "pairing",
      "groupPolicy": "allowlist",
      "groupAllowFrom": ["<cached-spectrum-group-space-id>"],
      "requireMention": true,
      "mentionNames": ["OpenClaw", "Assistant"],
      "sendReadReceipts": true,
      "typingIndicators": true,
      "inboundBatching": true,
      "inboundBatchDelayMs": 2500,
      "inboundBatchMaxDelayMs": 8000,
      "dispatchControlEvents": false,
      "dispatchPollVotes": true,
      "nativeActions": true,
      "dangerousNativeActions": false,
      "effectAck": "confirmed",
      "maxInboundAttachmentBytes": 20971520,
      "maxOutboundAttachmentBytes": 52428800
    }
  }
}
```

Secrets should come from environment variables or the local OpenClaw config
store. Do not commit project secrets.

Production defaults are intentionally conservative for a persistent iMessage
agent:

- `nativeActions=true` exposes Spectrum/iMessage-native actions such as
  reactions, read receipts, replies, edits, unsends, uploads, effects, polls,
  backgrounds, group controls, and mini-app cards through OpenClaw's shared
  `message` action surface.
- `dangerousNativeActions=false` keeps disruptive or user-affecting native
  operations owner-gated by default. Group rename/avatar/background, advanced
  poll mutation, sticker placement, location requests, and Notify Anyway are
  available to the command owner, and become generally available only when this
  flag is explicitly set to `true`. Mini-app cards are available for direct
  iMessage chats; sending a mini-app card into a group remains owner-gated.
- `sendReadReceipts=true` marks accepted inbound iMessages read best-effort in
  remote iMessage mode. Local mode does not send read receipts.
- `typingIndicators=true` refreshes the iMessage typing indicator about every 4
  seconds while long-running accepted messages are processed. It suppresses
  visible tool/progress chatter so the chat only gets the final reply.
  `progressUpdates` remains a backwards-compatible alias for older configs.
- `inboundBatching=true` waits briefly for close follow-up messages from the
  same chat before dispatching one combined agent turn. The default quiet window
  is `inboundBatchDelayMs=2500`, capped by `inboundBatchMaxDelayMs=8000` so a
  rapid burst cannot delay indefinitely.
- `dispatchControlEvents=false` records noisy lightweight controls such as
  typing without starting a fresh agent turn. Tapbacks and unsends are surfaced
  as normal inbound context because they are deliberate user-visible message
  events.
- `dispatchPollVotes=true` lets selected iMessage poll options start an agent
  turn while still suppressing poll deselection noise. This makes native polls a
  practical picker/choice interface. Set it to `false` when polls should remain
  passive telemetry only.
- `maxInboundAttachmentBytes=20971520` (20 MiB) limits inbound media cached into
  OpenClaw's media store. `maxOutboundAttachmentBytes=52428800` (50 MiB)
  rejects oversized local outbound files and buffers before handoff to Spectrum.
- iMessage bubble/screen effects and iOS text animations are available through
  `sendWithEffect`, but normal sends do not use effects by default.
- `effectAck="confirmed"` makes `sendWithEffect` wait for iMessage
  confirmation and return message ids. `effectAck="optimistic"` returns as soon
  as the effect send is handed off and records any later delivery failure in
  Photon status. Per-action `effectAck=optimistic` or `fast=true` overrides the
  account default.
- `miniAppDefaults` is optional. Use it only when you have real iMessage app
  extension metadata you want Photon to reuse for direct-chat mini-app cards.
  String fields can include `{{runId}}`, `{{phase}}`, `{{step}}`, `{{result}}`,
  or any matching action parameter. URL placeholders are percent-encoded.
- `groupPolicy="allowlist"` means groups are blocked unless their cached
  Spectrum group space id is listed in `groupAllowFrom`. With the production
  default `groupAllowFrom=[]`, all group chats are blocked until explicitly
  allowed. `requireMention=true` still applies to allowed groups.
- `photonDoctor` and `status` diagnostics report runtime state, cached and
  persisted spaces, message/reaction cache sizes, reconnect count and last
  reconnect time, stream/media/action errors, unsupported content markers, and
  last inbound/outbound timestamps, message ids, and space ids.

For local iMessage experiments, set:

```json
{
  "channels": {
    "photon": {
      "provider": "imessage",
      "local": true
    }
  }
}
```

Local mode avoids Photon cloud credentials but has fewer iMessage capabilities.
Cloud/dedicated Photon mode is the preferred target for a rich always-on agent
channel.

## Smoke Provider

Use the terminal provider to prove OpenClaw routing before iMessage auth:

```json
{
  "channels": {
    "photon": {
      "provider": "terminal",
      "dmPolicy": "open",
      "groupPolicy": "open"
    }
  }
}
```

## Safety Defaults

- DMs default to pairing.
- A new DM sender gets a pairing challenge. Approve it with
  `openclaw pairing approve photon <code>`.
- Groups default to allowlist + mention required.
- Proactive outbound can use a cached Spectrum space, an existing iMessage
  space id, or a bare E.164/email DM target that Spectrum can resolve/create.
- Bare phone/email targets are supported for DMs. Cold group sends still mostly
  require the group to speak first so the Spectrum space is cached; a dedicated
  group resolver/create flow is future work.
- Direct iMessage slash commands are surfaced as authorised OpenClaw text
  commands for paired/allowed DM senders, so commands such as `/goal`,
  `/status`, `/help`, and `/models` can use OpenClaw's normal command runtime
  instead of a Photon-specific command parser. If `commands.text=false`, Photon
  respects that and leaves slash text as ordinary message content.
- Photon also handles a few direct-only shortcuts before a model turn:
  `/doctor` or `/photon` returns Photon channel diagnostics, `/effects` lists
  supported iMessage effects and text animations, `/apps` summarises
  direct-chat app affordances and mini-app config readiness, `/effect <name>
  <message>` sends a native bubble/screen effect, and `/animate <name>
  <message>` sends an iOS text animation. Generic commands such as `/status`
  and `/help` deliberately stay with OpenClaw's normal command runtime.
- Inbound message ids are deduped in-memory for at-least-once stream replay.
- If the Spectrum message stream ends or throws, the channel re-subscribes with
  capped exponential backoff.
- Remote iMessage stream resilience mostly lives inside `spectrum-ts`. Keep the
  dependency current because recent versions add cursor-based catch-up, live
  buffering during catch-up, event dedupe, capped jittered reconnect, and
  persistent-failure escalation.
- Spectrum's internal `[spectrum.stream]` reconnect logs do not currently
  update Photon persisted runtime status, so `openclaw channels status --probe`
  can still be green during an internal stream reconnect storm. On local
  deployments, `scripts/photon-stream-watchdog.sh` watches the gateway log and
  restarts the gateway only after repeated or persistent stream degradation.
- Treat a single `[spectrum.stream] stream interrupted; reconnecting` line as
  degraded, not fatal. Treat repeated lines in a short window, persistent
  failure logs, fresh `PERMISSION_DENIED`, or fresh `Target not allowed` as
  actionable.
- Remote iMessage sends retry once after a transport drop by recreating the
  Spectrum app and resolving a fresh space. Inbound reply recovery falls back to
  an unthreaded send if the original threaded reply object came from the stale
  client. Keep this capped: Spectrum/iMessage does not expose a clear outbound
  idempotency key, so repeated retries can duplicate visible messages.
- `openclaw channels status --probe` includes Photon runtime status when the
  gateway asks the plugin to probe the account.
- `message(action=photonDoctor, channel=photon)` returns a JSON diagnostic with
  running state, cached spaces/messages/reaction handles, reconnect count and
  last reconnect time, last inbound/outbound timestamps, last
  inbound/outbound message and space ids, last stream/media/action error,
  unsupported content marker

... (truncated)
channels

Comments

Sign in to leave a comment

Loading comments...