fix(photon): preserve text in mixed iMessage attachments (salvage #46513) - #46818
Conversation
When an iMessage bubble carried both text and an attachment, spectrum-ts'
inbound mapper returned only buildAttachmentMessage(...), dropping the user's
typed text before Hermes could see it. The Photon adapter then had no 'group'
content path, so the text was lost entirely.
- adapter.py: handle a new 'group' content type that flattens text + attachment
items, preserving the typed text alongside cached media (extracted shared
_normalize_binary_payload helper).
- sidecar: emit 'group' content in normalizeContent, and ship
patch-spectrum-mixed-attachments.mjs which patches spectrum-ts' pinned mapper
(at npm postinstall AND at sidecar startup, so existing installs self-heal).
Windows robustness fixes on top of the original PR:
- The patcher's CLI guard used 'import.meta.url === file://${argv[1]}', which
never matches on Windows (file:/// + drive letter) — it silently no-opped.
Switched to pathToFileURL(argv[1]).href.
- The patcher matched \n-joined strings, so a CRLF checkout (Windows git
autocrlf) defeated every replacement. It now normalizes CRLF->LF for matching
and restores the original EOL style on write.
Co-authored-by: Yuhang Lin <yuhanglin@YuhangdeMac-mini.local>
🔎 Lint report:
|
| Rule | Count |
|---|---|
invalid-assignment |
1 |
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`
✅ Fixed issues (2):
| Rule | Count |
|---|---|
unresolved-attribute |
2 |
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_credits_session_start_micros` on type `AIAgent`
run_agent.py:2941: [unresolved-attribute] unresolved-attribute: Object of type `Self@get_credits_spent_micros` has no attribute `_credits_session_start_micros`
Unchanged: 5791 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
…imessage-46513-v2 # Conflicts: # scripts/release.py
OutThisLife
left a comment
There was a problem hiding this comment.
Approving this salvage. Confirmed the bug is still live on main (the adapter has no group/mixed path, and patch-spectrum-mixed-attachments.mjs doesn't exist), and the fix is sound:
adapter.py: the newgroupcontent type flattens text + attachment items, and the extracted_normalize_binary_payloadhelper keeps the existing single-attachment/voicebehavior byte-for-byte identical. Good refactor.- The runtime patch is well-guarded: idempotent via the
MARKERsentinel;replaceOnce/replaceFirstassert match counts and fail loud on drift; CRLF↔LF normalization handles Windowsautocrlfcheckouts; and thepathToFileURL(process.argv[1]).hrefCLI guard fixes the Windows no-op. spectrum-ts being pinned to an exact3.1.0is what makes exact-string patching of compiled output acceptable here. - Attribution preserved (AUTHOR_MAP + co-authored-by); regression tests cover both the patcher and the inbound
grouppath.
One non-blocking consideration: in index.mjs, a patchSpectrumTs() failure at startup process.exit(3)s the whole sidecar. With the exact 3.1.0 pin this won't fire in practice, but if the pin is ever bumped without updating the patch strings, the entire Photon channel goes dark at boot rather than degrading to the (pre-existing) text-dropping behavior for mixed messages only. Failing loud has merit — it surfaces the breakage instead of silently regressing — so I'd leave it as a maintainer judgment call; just flagging the blast radius. Not blocking.
…imessage-46513-v2-fix # Conflicts: # scripts/release.py
…sResearch#46513) (NousResearch#46818) * fix(photon): preserve text in mixed iMessage attachments When an iMessage bubble carried both text and an attachment, spectrum-ts' inbound mapper returned only buildAttachmentMessage(...), dropping the user's typed text before Hermes could see it. The Photon adapter then had no 'group' content path, so the text was lost entirely. - adapter.py: handle a new 'group' content type that flattens text + attachment items, preserving the typed text alongside cached media (extracted shared _normalize_binary_payload helper). - sidecar: emit 'group' content in normalizeContent, and ship patch-spectrum-mixed-attachments.mjs which patches spectrum-ts' pinned mapper (at npm postinstall AND at sidecar startup, so existing installs self-heal). Windows robustness fixes on top of the original PR: - The patcher's CLI guard used 'import.meta.url === file://${argv[1]}', which never matches on Windows (file:/// + drive letter) — it silently no-opped. Switched to pathToFileURL(argv[1]).href. - The patcher matched \n-joined strings, so a CRLF checkout (Windows git autocrlf) defeated every replacement. It now normalizes CRLF->LF for matching and restores the original EOL style on write. Co-authored-by: Yuhang Lin <yuhanglin@YuhangdeMac-mini.local> * chore: map YuhangLin contributor email for attribution (NousResearch#46513) --------- Co-authored-by: Yuhang Lin <yuhanglin@YuhangdeMac-mini.local> Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
…sResearch#46513) (NousResearch#46818) * fix(photon): preserve text in mixed iMessage attachments When an iMessage bubble carried both text and an attachment, spectrum-ts' inbound mapper returned only buildAttachmentMessage(...), dropping the user's typed text before Hermes could see it. The Photon adapter then had no 'group' content path, so the text was lost entirely. - adapter.py: handle a new 'group' content type that flattens text + attachment items, preserving the typed text alongside cached media (extracted shared _normalize_binary_payload helper). - sidecar: emit 'group' content in normalizeContent, and ship patch-spectrum-mixed-attachments.mjs which patches spectrum-ts' pinned mapper (at npm postinstall AND at sidecar startup, so existing installs self-heal). Windows robustness fixes on top of the original PR: - The patcher's CLI guard used 'import.meta.url === file://${argv[1]}', which never matches on Windows (file:/// + drive letter) — it silently no-opped. Switched to pathToFileURL(argv[1]).href. - The patcher matched \n-joined strings, so a CRLF checkout (Windows git autocrlf) defeated every replacement. It now normalizes CRLF->LF for matching and restores the original EOL style on write. Co-authored-by: Yuhang Lin <yuhanglin@YuhangdeMac-mini.local> * chore: map YuhangLin contributor email for attribution (NousResearch#46513) --------- Co-authored-by: Yuhang Lin <yuhanglin@YuhangdeMac-mini.local> Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
…sResearch#46513) (NousResearch#46818) * fix(photon): preserve text in mixed iMessage attachments When an iMessage bubble carried both text and an attachment, spectrum-ts' inbound mapper returned only buildAttachmentMessage(...), dropping the user's typed text before Hermes could see it. The Photon adapter then had no 'group' content path, so the text was lost entirely. - adapter.py: handle a new 'group' content type that flattens text + attachment items, preserving the typed text alongside cached media (extracted shared _normalize_binary_payload helper). - sidecar: emit 'group' content in normalizeContent, and ship patch-spectrum-mixed-attachments.mjs which patches spectrum-ts' pinned mapper (at npm postinstall AND at sidecar startup, so existing installs self-heal). Windows robustness fixes on top of the original PR: - The patcher's CLI guard used 'import.meta.url === file://${argv[1]}', which never matches on Windows (file:/// + drive letter) — it silently no-opped. Switched to pathToFileURL(argv[1]).href. - The patcher matched \n-joined strings, so a CRLF checkout (Windows git autocrlf) defeated every replacement. It now normalizes CRLF->LF for matching and restores the original EOL style on write. Co-authored-by: Yuhang Lin <yuhanglin@YuhangdeMac-mini.local> * chore: map YuhangLin contributor email for attribution (NousResearch#46513) --------- Co-authored-by: Yuhang Lin <yuhanglin@YuhangdeMac-mini.local> Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
…sResearch#46513) (NousResearch#46818) * fix(photon): preserve text in mixed iMessage attachments When an iMessage bubble carried both text and an attachment, spectrum-ts' inbound mapper returned only buildAttachmentMessage(...), dropping the user's typed text before Hermes could see it. The Photon adapter then had no 'group' content path, so the text was lost entirely. - adapter.py: handle a new 'group' content type that flattens text + attachment items, preserving the typed text alongside cached media (extracted shared _normalize_binary_payload helper). - sidecar: emit 'group' content in normalizeContent, and ship patch-spectrum-mixed-attachments.mjs which patches spectrum-ts' pinned mapper (at npm postinstall AND at sidecar startup, so existing installs self-heal). Windows robustness fixes on top of the original PR: - The patcher's CLI guard used 'import.meta.url === file://${argv[1]}', which never matches on Windows (file:/// + drive letter) — it silently no-opped. Switched to pathToFileURL(argv[1]).href. - The patcher matched \n-joined strings, so a CRLF checkout (Windows git autocrlf) defeated every replacement. It now normalizes CRLF->LF for matching and restores the original EOL style on write. Co-authored-by: Yuhang Lin <yuhanglin@YuhangdeMac-mini.local> * chore: map YuhangLin contributor email for attribution (NousResearch#46513) --------- Co-authored-by: Yuhang Lin <yuhanglin@YuhangdeMac-mini.local> Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
…sResearch#46513) (NousResearch#46818) * fix(photon): preserve text in mixed iMessage attachments When an iMessage bubble carried both text and an attachment, spectrum-ts' inbound mapper returned only buildAttachmentMessage(...), dropping the user's typed text before Hermes could see it. The Photon adapter then had no 'group' content path, so the text was lost entirely. - adapter.py: handle a new 'group' content type that flattens text + attachment items, preserving the typed text alongside cached media (extracted shared _normalize_binary_payload helper). - sidecar: emit 'group' content in normalizeContent, and ship patch-spectrum-mixed-attachments.mjs which patches spectrum-ts' pinned mapper (at npm postinstall AND at sidecar startup, so existing installs self-heal). Windows robustness fixes on top of the original PR: - The patcher's CLI guard used 'import.meta.url === file://${argv[1]}', which never matches on Windows (file:/// + drive letter) — it silently no-opped. Switched to pathToFileURL(argv[1]).href. - The patcher matched \n-joined strings, so a CRLF checkout (Windows git autocrlf) defeated every replacement. It now normalizes CRLF->LF for matching and restores the original EOL style on write. Co-authored-by: Yuhang Lin <yuhanglin@YuhangdeMac-mini.local> * chore: map YuhangLin contributor email for attribution (NousResearch#46513) --------- Co-authored-by: Yuhang Lin <yuhanglin@YuhangdeMac-mini.local> Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
…sResearch#46513) (NousResearch#46818) * fix(photon): preserve text in mixed iMessage attachments When an iMessage bubble carried both text and an attachment, spectrum-ts' inbound mapper returned only buildAttachmentMessage(...), dropping the user's typed text before Hermes could see it. The Photon adapter then had no 'group' content path, so the text was lost entirely. - adapter.py: handle a new 'group' content type that flattens text + attachment items, preserving the typed text alongside cached media (extracted shared _normalize_binary_payload helper). - sidecar: emit 'group' content in normalizeContent, and ship patch-spectrum-mixed-attachments.mjs which patches spectrum-ts' pinned mapper (at npm postinstall AND at sidecar startup, so existing installs self-heal). Windows robustness fixes on top of the original PR: - The patcher's CLI guard used 'import.meta.url === file://${argv[1]}', which never matches on Windows (file:/// + drive letter) — it silently no-opped. Switched to pathToFileURL(argv[1]).href. - The patcher matched \n-joined strings, so a CRLF checkout (Windows git autocrlf) defeated every replacement. It now normalizes CRLF->LF for matching and restores the original EOL style on write. Co-authored-by: Yuhang Lin <yuhanglin@YuhangdeMac-mini.local> * chore: map YuhangLin contributor email for attribution (NousResearch#46513) --------- Co-authored-by: Yuhang Lin <yuhanglin@YuhangdeMac-mini.local> Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
Summary
Salvage of #46513 (@1960697431 / YuhangLin) onto current
main, plus Windows-robustness fixes.When an iMessage bubble carried both text and an attachment, spectrum-ts' inbound mapper returned only
buildAttachmentMessage(...), dropping the user's typed text before Hermes could see it. The Photon adapter also had nogroupcontent path, so the text was lost entirely.Confirmed still present on
main:plugins/platforms/photon/adapter.pyhandles only singletextORattachment/voicecontent types — no mixed/group flattening — andpatch-spectrum-mixed-attachments.mjsdoesn't exist.What changed
groupcontent type that flattens text + attachment items, preserving typed text alongside cached media (extracted a shared_normalize_binary_payloadhelper).groupcontent innormalizeContent, and shipspatch-spectrum-mixed-attachments.mjswhich patches spectrum-ts' pinned mapper — at npmpostinstallAND at sidecar startup, so existing installs self-heal at runtime.test_spectrum_patch.py(patcher regression) + expandedtest_inbound.py.Windows-robustness fixes on top of the original PR
These two bugs made the patcher silently no-op on Windows, so I fixed them as part of the salvage:
import.meta.url === \file://${process.argv[1]}`, which never matches on Windows (file:///+ drive letter) — invoked as a subprocess it did nothing. Switched topathToFileURL(process.argv[1]).href`.\n-joined strings, so a CRLF checkout (Windows git autocrlf) defeated every replacement. It now normalizes CRLF→LF for matching and restores the original EOL style on write.Without these,
test_spectrum_patch.pyfails on Windows (the patcher exits 0 but inserts nothing).Test plan
pytest tests/plugins/platforms/photon/→ 90 passed. The only failures are 3 pre-existingtest_sidecar_lifecycle.py::test_reap_*cases — verified they also fail on pristinemain(Windows process-reaping limitation), and this PR doesn't touch that file.node --checkon the patcher + sidecar → OKgroupcontent inserted, returncode 0, CRLF preserved.Credit