Maintainer rework of #45580 (issue #54036) on top of the contributor's cherry-pick, which targeted spectrum-ts 3.1.0 while main pins 8.0.0: Sidecar (primary detection, new): - stream-staleness.mjs: pure decision rules, executable under node. * classifyProbeRejection: only a not-found-shaped rejection of the synthetic-id read counts as a completed round-trip (ALIVE); any other rejection is INCONCLUSIVE — never alive. The original /probe treated ANY rejection as alive, which was too loose. * shouldProbe: probe only after 10+ min of stream silence (configurable via PHOTON_STREAM_SILENCE_PROBE_MS; <=0 disables) with a cooldown. * isZombieSuspect: zombie only on silence past threshold AND a probe-proven live channel. Silence alone NEVER degrades (shared lines can be quiet for hours); inconclusive probes NEVER degrade (network may be down — the iterator will throw and the re-subscribe loop recovers on its own). - index.mjs: track last inbound-iterator yield (noteInboundYield), run a 30s watchdog tick, and on a confirmed zombie feed markStreamDegraded -> the existing exit-75 restart path. /healthz gains a stream.staleness block (silentForMs, threshold, lastProbeOutcome, zombieSuspected). /probe reworked to strict semantics: 200 only on a proven round-trip, 503 with outcome hung|inconclusive otherwise. Adapter (second layer, reworked): - _probe_once returns tri-state alive|hung|inconclusive; only a hung sidecar HTTP call counts toward the respawn counter — inconclusive resets nothing and triggers nothing. - default probe_interval_seconds 60 -> 600 (conservative; avoid restart storms on quiet lines). - _monitor_sidecar_health surfaces zombieSuspected from /healthz as a warning; the fatal UPSTREAM_STREAM_DEGRADED path is unchanged and fires when the sidecar escalates. Tests: test_zombie_stream_watchdog.py executes the real node decision module and drives the adapter against mocked /healthz responses; test_presence_watchdog.py updated for the tri-state probe. Also adds contributor mappings for nickkarhan (#53283) and vaibhavjnf (#45580). |
||
|---|---|---|
| .. | ||
| .gitignore | ||
| index.mjs | ||
| package-lock.json | ||
| package.json | ||
| patch-spectrum-mixed-attachments.mjs | ||
| README.md | ||
| send-format.mjs | ||
| stream-staleness.mjs | ||
Photon sidecar
Small Node helper that bridges Hermes Agent to Photon's Spectrum SDK
(spectrum-ts). Hermes is Python; Photon has no public HTTP
send-message endpoint today; replies therefore go through this sidecar.
The sidecar:
- runs
Spectrum({ projectId, projectSecret, providers: [imessage.config()] }) - exposes a loopback-only HTTP control channel for the Python adapter
to push send/typing requests (auth via
X-Hermes-Sidecar-Token) - drains the inbound message stream so
spectrum-tskeeps its reconnect/heartbeat machinery alive and Hermes can receive inbound messages over the adapter's loopbackGET /inboundstream
Install
cd plugins/platforms/photon/sidecar
npm install
The Hermes plugin's hermes photon setup command runs npm install
here automatically.
Run standalone
For debugging:
PHOTON_PROJECT_ID=... PHOTON_PROJECT_SECRET=... \
PHOTON_SIDECAR_PORT=8789 PHOTON_SIDECAR_TOKEN=$(openssl rand -hex 16) \
node index.mjs
In normal use, the Python adapter supervises this process — start, restart on crash, kill on shutdown — and never asks the user to run it by hand.
Why a sidecar at all?
Photon's Spectrum send path is exposed through the TypeScript SDK's
Space.send(...) API. Hermes is Python, so replies go through this sidecar
until Photon ships a public HTTP send endpoint.
When Photon ships an HTTP send endpoint, the plan is to retire this
sidecar entirely and call it directly from Python. The plugin's
outbound code path is already isolated behind small helpers
(_sidecar_send, _sidecar_send_richlink, and _sidecar_send_attachment in
adapter.py) to make that swap localized.