Open
Conversation
Mirrors the convos-ios single-inbox refactor (xmtplabs/convos-ios#713): one XMTP inbox per install, shared across every conversation and DM. Replaces the per-conversation identity model (ADR 002). Wire protocols are unchanged — invites, join requests, profile messages, and ExplodeSettings all interop with older CLI clients and with the iOS app before and after its own refactor. Version bumped to 0.8.0 (breaking). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ApprovabilityVerdict: Needs human review This PR implements a fundamental architectural change (ADR 011) replacing the per-conversation identity model with a single-inbox identity model per CLI install. The refactor touches core abstractions including identity storage, client creation/caching, and most conversation commands. While well-documented and internally consistent, major refactors that restructure shared infrastructure warrant human review. You can customize Macroscope's approvability policy. Learn more. |
…llow-ups Addresses the follow-ups surfaced by the convos-ios interop test run. Wire-level: - Register an ExplodeSettingsCodec so the CLI can decode the sender-hint message instead of logging "No codec found for content type". Filter ExplodeSettings out of displayable messages and emit an explode_notice event in agent serve when one is received. - Stop double-firing join_request_accepted: process-join-requests now tracks processed DM message IDs with a bounded LRU Set, so the batch pass and the live stream can't both emit for the same request. Watch mode observability (toward diagnosing the post-lock stall): - process-join-requests --watch now emits structured stream_started / stream_restarted / stream_ended events and verbose per-DM trace logs, so a silent stall is observable instead of invisible. New commands: - conversation explode-status: dumps appData expiresAtUnix plus the most recent ExplodeSettings message for interop verification. - conversation download-profile-image: fetches a member's encrypted profile image and decrypts it using the group's imageEncryptionKey. UX: - conversations list hides inactive conversations by default; pass --include-inactive to include exploded / removed groups. - convos init now writes a commented CONVOS_API_KEY line and prints a hint about it. README documents that the key is required for >1MB attachments and for profile images. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- explode.ts: confirmation message claimed "and leave the group" but the CLI can't call leaveGroup() (node-sdk limitation). Corrected the wording and surface the limitation explicitly. - README.md: drop the stale `convos identity info <id>` example — the command takes no positional arg after the single-inbox refactor. - conversations/create.ts + conversation/invite.ts: gate QR output on this.jsonOutput instead of flags.json so CONVOS_JSON_OUTPUT and --fields can't corrupt the JSON stream with a pre-flight QR code. - process-join-requests.ts: batch mode now updates lastDmTimestampNs so an onRestart that fires before any live-stream message lands has a non-zero sinceNs and doesn't silently skip catchup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Mirrors the convos-ios single-inbox refactor (xmtplabs/convos-ios#713). Every CLI install now has one XMTP inbox shared across every conversation and DM, replacing the per-conversation identity model (ADR 002 → ADR 011).
For the primary CLI user — agents and bots — this means one agent identity across every conversation it participates in, matching the iOS app's behavior and eliminating the multi-identity coordination layer.
Wire protocols unchanged. Invites, join requests, ProfileUpdate/ProfileSnapshot, ExplodeSettings, TypingIndicator, and JoinRequest all interop with older CLI clients and with iOS on either side of its refactor. An
ExplodeSettingsCodecis now registered so the CLI decodes the sender-hint message instead of logging "No codec found".Version bumped to 0.8.0 (breaking).
Breaking changes
~/.convos/identity.json+db/<env>/main.db3replacesidentities/<id>.json+db/<env>/<id>.db3. Existing layouts are not auto-migrated; runconvos resetto wipe legacy files.conversations create|joinstop minting per-conversation identities;identity list|info|removeoperate on zero-or-one;identity createerrors if an identity exists;conversations listoutput shape changed and filtersisActive: falseby default;agent servedrops--identity.ExplodeSettings+removeMembers(all others). Does not destroy the install's identity (per ADR 004 C9 amendment). TheleaveGroup()step from the iOS spec isn't available in@xmtp/node-sdk; receivers still drop on the MLS remove commit.disableDeviceSync: trueis gone — XMTP history sync now works across installs sharing the same inbox.createClientForIdentity→getClient/getIdentityAndClient(cached singleton per(home, env)).IdentityStorecollapsed toload/loadOrUpsert/exists/update/delete/getDbPath. New exports for the ExplodeSettings codec.What's on the branch
Core refactor (commit 1)
IdentityStorebacked by~/.convos/identity.json. All 22 conversation commands migrated togetClient/getIdentityAndClient. Explode rewritten to remove-all-and-hang-up. README, CHANGELOG, skill, and docstrings rewritten for the new model.Post-review hardening (commit 1, after four parallel code reviews)
identity.json— advisory lock around read-modify-write so two CLI processes against the sameCONVOS_HOMEcan't lose updates. 10s timeout, 30s stale-lock break,Atomics.waitsleep. Writes are atomic (write-then-rename) so readers never see a truncated file.onRestartused to drop a second restart if one was already in flight. Now uses a scheduler + pending-flag pattern so nothing is lost.process-join-requests --watchcatchup — previously had noonRestarthandler; now matches the agent-serve pattern.stream.return()instead of fire-and-forget.errorevents instead of silentcatch {}.encodeExplodeSettingsintosrc/utils/explodeSettings.ts.loadOrCreate→loadOrUpsertto make field-overwrite semantics explicit.iOS interop follow-ups (commit 3)
Informed by a cross-client test pass between the iOS single-inbox build and CLI 0.8.0.
ExplodeSettingsCodecregistered on the XMTP client. Filtered out of displayable messages.agent serveemits a newexplode_noticendjson event when one arrives.process-join-requeststracks processed DM message IDs in a bounded LRU Set. Batch pass + live stream can no longer double-fire for the same invite (fixes a--timeout 60mis-synchronization symptom the iOS agent saw).process-join-requests --watchnow emits structuredstream_started/stream_restarted/stream_endedevents and per-DM verbose trace lines (--verbose). Aimed at making a silent stall diagnosable (one was reported but not repro'd).convos conversation explode-status <id>— dumps appDataexpiresAtUnix+ the most recentExplodeSettingsmessage. Unblocks "iOS setsexpiresAton invite → CLI joins → CLI reads it back" verification.convos conversation download-profile-image <conv-id> <inbox-id>— fetches a member's encrypted profile image and decrypts via the group'simageEncryptionKey.conversations listfiltersisActive: falseby default —--include-inactiveopts them back in.convos init+ README — documentsCONVOS_API_KEYas required for attachments over 1MB and profile images; init writes a commented line with a hint.Known limitations
leaveGroup()(not exposed in@xmtp/node-sdk). iOS receivers drop on the ExplodeSettings message or MLS remove commit either way, but the CLI creator's local state stays as a non-empty group.identities/*.jsonfiles are ignored by the new store;convos resetcleans them up.Test plan
npm run typecheck— cleannpm test— 275/275 passCONVOS_HOME— verify lock contention surfaces a clear error rather than silent corruptionprocess-join-requests --watchafter lock sequence — the newstream_*events should make the cause observableRelated
🤖 Generated with Claude Code
Note
Refactor all CLI commands to use a single install-wide XMTP inbox identity (ADR 011)
createClientForIdentity+ identity store lookups) with a singletongetClient/getIdentityAndClientacross allconversation/*andconversations/*commands, agent serve, and identity management commands.src/utils/identities.tswith a new single-identity store API (load,loadOrUpsert,exists,update,delete,getDbPath(env)) backed by a singleidentity.jsonfile and a shared DB path per install/env; concurrent CLI processes are serialized via an advisory file lock.src/utils/client.tsso repeatedgetClientcalls for the same(homeDir, env)reuse a cached XMTP client, and registersExplodeSettingsCodecat client creation.ExplodeSettingscontent type support insrc/utils/explodeSettings.tswith codec, encode/detect/extract helpers, and filters these messages from displayable chat content.conversation download-profile-image(decrypt and save a member's profile image) andconversation explode-status(report explode state from metadata and recent messages).conversations listcommand now fetches directly from the singleton inbox with new--include-dmsand--include-inactiveflags; output fields are conversation-centric rather than identity-centric.IdentityStoreandIdentityAPIs are incompatible with 0.7.x; per-conversation identity fields (conversationId,inviteTag) are removed and existing multi-identity stores require migration per the 0.8.0 changelog.Macroscope summarized e35d5d6.