Skip to content

fix(photon): pull upstream v8 recovery fixes - #3

Merged
ragnos-dev merged 10 commits into
ragnos/mainfrom
codex/photon-v8-recovery-2026-06-28
Jun 28, 2026
Merged

ragnos-dev merged 10 commits into
ragnos/mainfrom
codex/photon-v8-recovery-2026-06-28

Conversation

@ragnos-dev

Copy link
Copy Markdown

Summary

  • cherry-pick upstream Photon recovery fixes for Envoy overflow retries, typing cooldown, degraded stream recovery, sidecar crash detection, CatchUpEvents labeling, console stream interruption escalation, and Spectrum sidecar v8
  • preserve RAGnos inbound heartbeat stamping while adding upstream stream health tracking
  • add own-message reply context support so Photon tapbacks on bot replies preserve target context

Verification

  • scripts/run_tests.sh tests/plugins/platforms/photon tests/gateway/test_platform_reconnect.py tests/gateway/test_reply_to_injection.py -- -q: 145 passed, 0 failed
  • scripts/run_tests.sh tests/plugins/platforms/photon/test_spectrum_patch.py tests/plugins/platforms/photon/test_overflow_recovery.py tests/plugins/platforms/photon/test_reactions.py tests/gateway/test_platform_reconnect.py tests/gateway/test_reply_to_injection.py -- -q: 68 passed, 0 failed
  • npm ci in plugins/platforms/photon/sidecar: install succeeded
  • node --check plugins/platforms/photon/sidecar/index.mjs
  • node --check plugins/platforms/photon/sidecar/patch-spectrum-mixed-attachments.mjs
  • git diff --check ragnos/main..HEAD

Notes

  • npm reports 17 moderate audit warnings from the sidecar dependency tree after installing Spectrum v8. I did not run npm audit fix because that would mutate upstream dependency choices outside this recovery pull.

JoaoMarcos44 and others added 10 commits June 28, 2026 14:51
…cooldown

Closes NousResearch#50185

Two independent gaps let a transient Photon/Spectrum upstream overflow
degrade message delivery and amplify gRPC pressure:

1. _is_retryable_error did not recognise Photon- or Envoy-specific error
   strings ("internal sidecar error", "upstream connect error",
   "reset reason: overflow"), so _send_with_retry fell through to the
   plain-text fallback immediately instead of backing off and retrying.

2. send_typing had no rate gate, so a burst of typing-indicator calls
   during an overflow event kept hitting the upstream gRPC connection and
   widened the failure window.

Fix:
- Add _PHOTON_RETRYABLE_PATTERNS with the three high-specificity Envoy /
  sidecar substrings and override _is_retryable_error on PhotonAdapter to
  check them after delegating to the base-class patterns.  base.py and all
  other adapters are untouched.
- Add a 5 s per-chat cooldown in send_typing backed by _typing_last_sent.
  stop_typing clears the entry so the next start after a completed turn
  fires immediately — only rapid consecutive starts without a stop are
  suppressed.
- Reduce PhotonAdapter._send_with_retry default max_retries from 2 to 1
  (single 2 s back-off check) — enough to confirm whether the Envoy
  circuit-breaker has opened, without adding unnecessary latency.

All changes are scoped to plugins/platforms/photon/adapter.py.
When the Node spectrum-ts sidecar process exited mid-session (crash,
OOM, upstream overflow escalation), _supervise_sidecar returned
silently — readline hit EOF, the log-pump loop broke, and nothing
notified the gateway. _inbound_loop entered an infinite retry loop
against a dead port, _running stayed True, and the adapter remained
in self.adapters with no path to self-recovery short of a manual
gateway restart.

Add a death-detection tail to _supervise_sidecar: after the log-pump
exits (EOF or exception), guard on _inbound_running to distinguish
unexpected death from a deliberate disconnect(). On unexpected exit,
call _set_fatal_error("SIDECAR_CRASHED", retryable=True) followed by
_notify_fatal_error() so the reconnect watcher picks up the platform
within 30 s and retries with exponential backoff (30 s → 300 s cap)
until the sidecar comes back up. All other platforms remain unaffected.

The _inbound_running guard is safe against races: disconnect() sets
_inbound_running = False before _stop_sidecar() cancels the supervisor
task. CancelledError is BaseException, not Exception, so it bypasses
the except clause and propagates normally — the detection block never
runs during a clean shutdown.
…tection

Follow-up for salvaged PR NousResearch#50256. Unit tests for the three behaviors:
retryable classification of Envoy/sidecar overflow strings, per-chat typing
cooldown with stop_typing reset, and the _supervise_sidecar crash-detection
path that raises a retryable fatal (and the clean-shutdown no-op).
…alate

spectrum-ts routes stream telemetry through @photon-ai/otel's createLogger,
which sends severity>=ERROR to console.error and WARN/INFO to console.log.
The two lines the health monitor keys off land on different channels:
log.error("stream persistently failing") -> console.error (caught), but
log.warn("stream interrupted; reconnecting") -> console.log (was missed).

The original interception patched console.error only, so the recovering->
degraded escalation counter never saw the interrupt bursts that are the
primary silent-inbound symptom. Verified live against spectrum-ts 3.1.0 +
@photon-ai/otel: 3 real log.warn('stream interrupted') calls now escalate
to degraded -> process.exit(75) -> adapter reconnect.

Adds a shared classifyStreamLog() fed by both console.error and console.log,
plus a regression test asserting both channels are intercepted.
Update the Photon platform plugin's Node.js sidecar from spectrum-ts
3.1.0 to 7.0.0, which splits the SDK into scoped `@spectrum-ts/*`
packages with `spectrum-ts` as the umbrella re-export.

- Bump exact pin in package.json/package-lock.json to 7.0.0
- Update mixed-attachments patch script to target the new
  `@spectrum-ts/imessage/dist/index.js` path and tab-indented output
- Rewrite test fixture to match v7.x mapper shape (tab-indented,
  `const ... = async` declarations, single-line builder calls) and
  point at `@spectrum-ts/imessage/dist/index.js`
- Update README upgrade guide to document the v5 package split and
  the postinstall patch validation step
- Update comments in cli.py and index.mjs to reference v5/v7 changes
v8 made `richlink` outbound-only; inbound rich links now arrive as
plain `text`. Remove the `getBalloonBundleId`/`toRichlinkMessage`
branches from the iMessage mapper patch and update the fixture,
lockfile, and README accordingly.
Populate `reply_to_message_id`, `reply_to_text`, and
`reply_to_is_own_message` on reaction events so the gateway injects
`[Replying to your previous message: "..."]` when the agent receives
a tapback.

The sidecar now extracts a capped text preview from the hydrated
reaction target (plain text and mixed group messages; null for
attachment/voice-only targets), emitting it as `targetText` in the
NDJSON reaction payload. The Python adapter reads this field and sets
the reply correlation fields on the `MessageEvent`.
@ragnos-dev
ragnos-dev merged commit 9cb51fc into ragnos/main Jun 28, 2026
7 checks passed
@ragnos-dev
ragnos-dev deleted the codex/photon-v8-recovery-2026-06-28 branch June 28, 2026 19:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants