feat(photon): gRPC-native iMessage channel (persistent connection, no webhook) - #42444
Closed
underthestars-zhy wants to merge 14 commits into
Closed
feat(photon): gRPC-native iMessage channel (persistent connection, no webhook)#42444underthestars-zhy wants to merge 14 commits into
underthestars-zhy wants to merge 14 commits into
Conversation
Make Photon iMessage a first-class persistent-connection channel like Discord/Slack, using the spectrum-ts gRPC stream for both directions. - Inbound: the sidecar forwards the SDK's app.messages gRPC stream to the adapter over a loopback GET /inbound (NDJSON) instead of webhooks. Drops the aiohttp webhook server, HMAC signature verification, public URL, and PHOTON_WEBHOOK_* config; adapter reconnects with backoff. - Management plane: device login uses client_id=photon-cli against the single dashboard host (Bearer), matching the official photon-hq/cli; find-or-create "Hermes Agent" project, enable Spectrum, rotate secret, register user (with phone dedup), surface the assigned iMessage line. - SDK projectId is the project's spectrumProjectId, not the dashboard id; runtime creds persist to ~/.hermes/.env like every other channel. - CLI: 6-step setup, webhook subcommands removed. - Tests/docs updated for the gRPC flow; sidecar pins spectrum-ts ^1.17.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On shared-number plans, `/lines` has no dedicated entry, so the `assignedPhoneNumber` field on the user object is the source of truth for which number to text the agent. Fall back to the line inventory only when no per-user assignment exists.
During `hermes photon setup`, allowlist the operator's number and set their DM as the cron home channel when those env vars are unset. Without this, the gateway denies the operator's own messages and cron has no default delivery target. Re-runs never overwrite hand-tuned values. Also teaches the sidecar's `resolveSpace` to accept a bare E.164 number as a space identifier, resolving it to the user's DM space so `PHOTON_HOME_CHANNEL` can be set to a phone number instead of an opaque space id.
Allow PHOTON_HOME_CHANNEL to accept a bare E.164 phone number or a `any;-;+1...` DM chat GUID in addition to a Spectrum space id. Inbound DM spaces are cached so replies resolve without a second SDK lookup, and `photon` is added to _PHONE_PLATFORMS so send_message treats E.164 strings as explicit targets rather than falling through to channel-name resolution.
Replace raw `{ replyTo }` send options with the `spectrumReply` content
builder from spectrum-ts, which is the correct API for threading
replies.
Adds `maybeReplyContent` helper with graceful fallback to normal send
when
the reply target cannot be resolved.
Drop `replyTo` from all outbound send paths and update the `/typing`
endpoint to use the documented `typing("start" | "stop")` content
builder. Adds a `stop_typing` method on the adapter to pair with
`send_typing`.
3 tasks
underthestars-zhy
marked this pull request as ready for review
June 9, 2026 02:59
Store operator and assigned iMessage numbers in `auth.json` after setup, and surface them in `hermes photon status`. When numbers are missing, status auto-refreshes from the dashboard without provisioning new lines.
teknium1
added a commit
that referenced
this pull request
Jun 9, 2026
Salvage follow-up for PR #42444 — maps the contributor's commit email so the changelog generator can attribute the Photon gRPC channel work.
19 tasks
changman
pushed a commit
to changman/hermes-agent
that referenced
this pull request
Jun 10, 2026
Salvage follow-up for PR NousResearch#42444 — maps the contributor's commit email so the changelog generator can attribute the Photon gRPC channel work.
alt-glitch
pushed a commit
that referenced
this pull request
Jun 14, 2026
Salvage follow-up for PR #42444 — maps the contributor's commit email so the changelog generator can attribute the Photon gRPC channel work.
14 tasks
davidgut1982
pushed a commit
to davidgut1982/hermes-agent
that referenced
this pull request
Jun 17, 2026
Salvage follow-up for PR NousResearch#42444 — maps the contributor's commit email so the changelog generator can attribute the Photon gRPC channel work.
T02200059
pushed a commit
to T02200059/hermes-agent
that referenced
this pull request
Jun 18, 2026
Salvage follow-up for PR NousResearch#42444 — maps the contributor's commit email so the changelog generator can attribute the Photon gRPC channel work.
xyshanren
pushed a commit
to xyshanren/hermes-agent-cn
that referenced
this pull request
Jun 25, 2026
Salvage follow-up for PR NousResearch#42444 — maps the contributor's commit email so the changelog generator can attribute the Photon gRPC channel work.
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
Salvage follow-up for PR NousResearch#42444 — maps the contributor's commit email so the changelog generator can attribute the Photon gRPC channel work.
santhreal
pushed a commit
to santhreal/hermes-agent
that referenced
this pull request
Jul 13, 2026
Salvage follow-up for PR NousResearch#42444 — maps the contributor's commit email so the changelog generator can attribute the Photon gRPC channel work.
donbowman
pushed a commit
to donbowman/hermes-agent
that referenced
this pull request
Jul 13, 2026
Salvage follow-up for PR NousResearch#42444 — maps the contributor's commit email so the changelog generator can attribute the Photon gRPC channel work.
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
Salvage follow-up for PR NousResearch#42444 — maps the contributor's commit email so the changelog generator can attribute the Photon gRPC channel work.
leewenjie
pushed a commit
to leewenjie/hermes-agent
that referenced
this pull request
Aug 7, 2026
Salvage follow-up for PR NousResearch#42444 — maps the contributor's commit email so the changelog generator can attribute the Photon gRPC channel work.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Reworks the Photon iMessage platform plugin into a first-class persistent-connection channel — like Discord and Slack — backed by the
spectrum-tsgRPC stream for both inbound and outbound.Previously Photon relied on inbound webhooks (a public URL, an aiohttp server, and HMAC signature verification) plus a separate management/login flow. That model is fragile (requires a reachable public URL), doesn't match how every other Hermes channel onboards, and couldn't deliver real attachments.
The new design holds a long-lived gRPC stream to Photon inside a small supervised Node sidecar (because
spectrum-tsis TypeScript-only) and talks to the Python adapter over loopback. The whole webhook stack is gone, onboarding collapses to a singlehermes photon setup, and attachments now work in both directions.Related Issue
Fixes #
Type of Change
Changes Made
Inbound: gRPC stream instead of webhooks
app.messagesgRPC stream, normalizes each message, and streams it to the adapter over a loopbackGET /inbound(NDJSON). —plugins/platforms/photon/sidecar/index.mjsPhotonAdapterconsumes the NDJSON stream, dedupes onmessageId, dispatchesMessageEvent, and reconnects with backoff if the stream drops (the sidecar owns the gRPC reconnect to Photon). —plugins/platforms/photon/adapter.pyPHOTON_WEBHOOK_*config. —adapter.py,plugin.yamlOutbound: spectrum-ts send path
send/send_typingare loopback POSTs to the sidecar (/send,/typing), authenticated with a sharedX-Hermes-Sidecar-Token. —adapter.py,sidecar/index.mjsspectrumTexthelper; threaded replies use the spectrum-ts reply builder. —sidecar/index.mjsadapter.py,tools/send_message_tool.pyAttachments (both directions)
content.read()), base64-inlined on the NDJSON event, cached to the shared media cache, and exposed viamedia_urls/media_types(vision included) — parity with BlueBubbles. Oversized (default 20 MB) or failed reads fall back to a text marker. —sidecar/index.mjs,adapter.pyspace.send(attachment(...))/space.send(voice(...))through the sidecar's attachment endpoint. —sidecar/index.mjsManagement plane & setup
client_id=photon-cliagainst the single dashboard host, matching the officialphoton-hq/cli. —auth.pyHermes Agentproject, enables Spectrum, reads the project'sspectrumProjectId(distinct from the dashboard id — the SDK authenticates with the spectrum id), rotates the project secret, and registers the user with phone dedup. —auth.py,cli.pyPHOTON_PROJECT_ID,PHOTON_PROJECT_SECRET) persist to~/.hermes/.envlike every other channel; management metadata lives in~/.hermes/auth.json. —auth.pyhermes photon setupis now a single 6-step flow; the separateloginandwebhooksubcommands are removed. Setup also auto-configures the allowlist and home/cron channel and prints the assigned iMessage line. —cli.pyDocs & deps
plugins/platforms/photon/README.md,website/docs/user-guide/messaging/photon.mdspectrum-ts ^1.17.1. —sidecar/package.json,sidecar/package-lock.jsonHow to Test
setup→ the agent replies over the gRPC stream (no public URL / webhook needed).space.send(attachment(...)).Automated:
pytest tests/plugins/platforms/photon/ tests/tools/test_send_message_target_parse.py tests/tools/test_send_message_tool.py -q # 62 passed, 1 skippedChecklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass (Photon scope: 62 passed, 1 skipped)Documentation & Housekeeping
docs/, docstrings) — plugin README + website messaging docscli-config.yaml.exampleif I added/changed config keys — env vars documented inplugin.yamlCONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/Asend_message_tooltarget parsingScreenshots / Logs