Channels
Photon
OpenClaw channel plugin for Photon/Spectrum iMessage
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