Skip to content

Multi-peer mapping config#31

Open
spooktheducks wants to merge 2 commits intoplastic-labs:mainfrom
allora-network:feat/multi-peer-mappings
Open

Multi-peer mapping config#31
spooktheducks wants to merge 2 commits intoplastic-labs:mainfrom
allora-network:feat/multi-peer-mappings

Conversation

@spooktheducks
Copy link
Copy Markdown

@spooktheducks spooktheducks commented Mar 26, 2026

Summary by CodeRabbit

  • New Features

    • Multi-peer model: per-participant human peers with auto-creation for unmapped group-chat senders and persistent mappings.
    • Session-aware peer routing across CLI, tools, and session/context flows; added a --peer option to relevant commands.
    • Agent peer overrides via agentPeerMappings.
  • Documentation

    • Updated config docs with peerMappings and agentPeerMappings and a “Peer Mappings” section describing behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 26, 2026

Walkthrough

This PR replaces the single global ownerPeer with a multi-peer model: per-session/channel human peers plus per-agent peers. It adds peerMappings and agentPeerMappings, extracts sender IDs from message metadata, and updates state, hooks, tools, CLI, and message processing to resolve and use human peers.

Changes

Cohort / File(s) Summary
Docs & Schema
README.md, openclaw.plugin.json
Added peerMappings and agentPeerMappings docs and schema entries; updated README to describe the Multi-Peer Model and mapping behavior.
Config
config.ts
Added peerMappings and agentPeerMappings to HonchoConfig and parsing logic via parseStringRecord.
Core State Management
state.ts
Removed ownerPeer; added humanPeers cache, persisted humanPeerMap, and methods getHumanPeer, resolveSessionHumanPeer, and isHumanPeerId. Initialization/persistence updated to load/save mappings.
Message Extraction & Helpers
helpers.ts
Added extractSenderId(content); changed extractMessages signature to accept defaultHumanPeer and optional resolvePeer callback; selection of message peer now per-sender or agent.
Capture Hook
hooks/capture.ts
Extracts raw content, derives sender IDs, batch-resolves human peers, builds peer configs per-batch, updates session metadata with humanPeerIds, and calls extractMessages with resolver.
Context Hook
hooks/context.ts
Resolves humanPeer via session metadata and uses it as peerTarget for agentPeer.context/session.context instead of ownerPeer.
CLI
commands/cli.ts
honcho ask and honcho search accept --peer and use state.getHumanPeer(...) (or resolved human peer) instead of state.ownerPeer!.
Tools — Routing & Context
tools/ask.ts, tools/context.ts, tools/search.ts, tools/message-search.ts, tools/session.ts
Tools now resolve a session-specific human peer via state.resolveSessionHumanPeer(buildSessionKey(...)) and use it for agentPeer.chat, representation, card, and speaker attribution. Many tools changed to factory callbacks for dynamic registration. Speaker labeling now uses state.isHumanPeerId(...).
Tool Behavior — ask
tools/ask.ts
agentPeer.chat target changed from global owner to session-resolved human peer.
Tool Behavior — message search
tools/message-search.ts
Search “from=user” now resolves session human peer; speaker labels use isHumanPeerId.

Sequence Diagram

sequenceDiagram
    participant Batch as Message Batch
    participant Extract as extractSenderId
    participant State as PluginState
    participant Honcho as Honcho (Peers)
    participant Session as Session/Tool

    Batch->>Extract: provide rawContent with metadata block
    Extract->>Extract: parse fenced JSON, return sender_id
    Extract->>State: resolve/getHumanPeer(sender_id)
    State->>State: check humanPeers cache / humanPeerMap
    alt cached
        State-->>Extract: return existing human peer
    else create
        State->>Honcho: create new human peer
        Honcho-->>State: peer created
        State-->>Extract: cache and return new peer
    end
    Extract->>Session: attach resolved peer to message
    Session->>Honcho: use humanPeer and agentPeer for chat/representation/observe
    Honcho-->>Session: responses routed per resolved peers
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐇 I nibble metadata in the night,

each sender gets a peer in sight—
agents twirl in agent-{id} rings,
group chat blooms with many springs,
Honcho hops along, all right!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding configuration support for multi-peer mappings (peerMappings and agentPeerMappings) throughout the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@spooktheducks spooktheducks force-pushed the feat/multi-peer-mappings branch from 0360758 to a3f828e Compare March 26, 2026 12:53
@spooktheducks spooktheducks marked this pull request as ready for review March 26, 2026 13:47
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
config.ts (1)

37-45: Normalize mapping IDs during config parsing.

parseStringRecord() currently keeps leading/trailing whitespace and silently drops malformed entries. Because missing mappings fall back to default or auto-created peer IDs, a stray space or bad value here can split memory across different peers and be hard to diagnose. Trim both sides before the emptiness check, and consider failing fast on invalid entries.

♻️ Possible normalization
 function parseStringRecord(raw: unknown): Record<string, string> {
   if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
   const result: Record<string, string> = {};
-  for (const [k, v] of Object.entries(raw as Record<string, unknown>)) {
-    if (typeof k === "string" && typeof v === "string" && k.length > 0 && v.length > 0) {
-      result[k] = v;
+  for (const [rawKey, rawValue] of Object.entries(raw as Record<string, unknown>)) {
+    if (typeof rawValue !== "string") continue;
+    const key = rawKey.trim();
+    const value = rawValue.trim();
+    if (key.length > 0 && value.length > 0) {
+      result[key] = value;
     }
   }
   return result;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config.ts` around lines 37 - 45, parseStringRecord currently keeps
leading/trailing whitespace and silently drops malformed entries; update it to
trim both key and value before validating emptiness (use trimmedK/trimmedV for
checks and insertion) and change failure behavior to fail fast on invalid
entries (e.g., throw an Error or surface a clear log message) instead of
silently ignoring them; update the loop over Object.entries(raw as
Record<string, unknown>) and the result assignment to use the trimmed strings
and add a validation branch that raises an explicit error when encountering
non-string values or empty strings after trimming so misconfigurations are
detected early.
state.ts (1)

113-142: Potential race condition on concurrent peer creation.

If two requests with the same new channelPeerId arrive concurrently before either completes, both will miss the cache check (line 125-126), create duplicate Honcho peer requests, and both will attempt to persist the mapping. While the outcome is functionally correct (last write wins, peer is idempotent), this creates unnecessary API calls and metadata writes.

Consider adding a pending-creation map to deduplicate in-flight requests:

♻️ Optional: Deduplicate concurrent peer creation
+  const pendingPeers = new Map<string, Promise<Peer>>();
+
   async function getHumanPeer(channelPeerId?: string): Promise<Peer> {
     if (!channelPeerId) {
       // Return default owner peer
       let peer = state.humanPeers.get(OWNER_ID);
       if (!peer) {
         peer = await honcho.peer(OWNER_ID, { metadata: {} });
         state.humanPeers.set(OWNER_ID, peer);
       }
       return peer;
     }
 
     // Check cache
     let peer = state.humanPeers.get(channelPeerId);
     if (peer) return peer;
 
+    // Check pending creation
+    const pending = pendingPeers.get(channelPeerId);
+    if (pending) return pending;
+
+    const createPeer = async (): Promise<Peer> => {
       // Resolve honcho peer ID from mapping or use channel peer ID directly
       let honchoId = state.humanPeerMap[channelPeerId];
       if (!honchoId) {
         honchoId = channelPeerId;
         // Persist auto-created mapping
         state.humanPeerMap[channelPeerId] = honchoId;
         const wsMeta = await honcho.getMetadata();
         await honcho.setMetadata({ ...wsMeta, humanPeerMap: state.humanPeerMap });
         api.logger.info(`[honcho] Auto-created human peer mapping: "${channelPeerId}" → "${honchoId}"`);
       }
 
-    peer = await honcho.peer(honchoId, { metadata: { channelPeerId } });
-    state.humanPeers.set(channelPeerId, peer);
-    return peer;
+      const p = await honcho.peer(honchoId, { metadata: { channelPeerId } });
+      state.humanPeers.set(channelPeerId, p);
+      return p;
+    };
+
+    const promise = createPeer().finally(() => pendingPeers.delete(channelPeerId));
+    pendingPeers.set(channelPeerId, promise);
+    return promise;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@state.ts` around lines 113 - 142, getHumanPeer currently has a race where
concurrent calls for the same new channelPeerId can each miss state.humanPeers
and create duplicate honcho.peer and honcho.setMetadata calls; add a
pending-creation dedupe: introduce a Map<string, Promise<Peer>> (e.g.,
pendingHumanPeers) checked immediately after the cache miss, store the in-flight
Promise before any await (so subsequent callers return the same Promise),
perform the honcho.peer/honcho.setMetadata work inside that Promise, and finally
remove the entry from pendingHumanPeers and set state.humanPeers once resolved;
keep existing OWNER_ID short-circuit unchanged.
hooks/capture.ts (1)

13-33: Minor duplication with helpers.ts content extraction.

getRawContent duplicates the content extraction logic in extractMessages (helpers.ts lines 222-236). Consider extracting a shared helper if this pattern appears elsewhere.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/capture.ts` around lines 13 - 33, getRawContent duplicates the message
content extraction already implemented in extractMessages (helpers.ts); refactor
by extracting the shared logic into a single exported helper (e.g.,
extractRawTextFromMessage or normalizeMessageContent) in helpers.ts (or a new
utils module), update getRawContent to call that helper, and replace other
similar extraction sites to import and reuse the new helper; ensure the helper
accepts unknown/msg: unknown and preserves current behavior for string,
array-of-blocks, and fallback to "" so callers like getRawContent and
extractMessages remain functionally identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@commands/cli.ts`:
- Around line 264-265: The CLI ask/search commands call state.getHumanPeer()
with no argument, which falls back to the owner peer and prevents targeting a
specific session human; update the CLI handlers that call agentPeer.chat (the
lines using const humanPeer = await state.getHumanPeer(); const answer = await
agentPeer.chat(question, { target: humanPeer }); and the similar block around
lines 280-281) to accept an optional peer selector flag/argument (e.g., --peer
or --human) and pass that identifier into state.getHumanPeer(peerId) so the
selected peer is resolved and then call agentPeer.chat(question, { target:
humanPeer }) with the resolved peer; ensure the CLI help text and parsing
include the new flag and preserve the fallback behavior when the flag is
omitted.

In `@hooks/capture.ts`:
- Around line 143-151: The current code only writes lastSenderId into session
metadata as humanPeerId, losing information about other resolved participants;
update the metadata write (the updatedMeta object where humanPeerId is set) to
persist all resolved human participant peers instead—e.g., add a humanPeerIds
(or humanPeers) field containing the list/map of resolved peer IDs from the same
data used to build peerConfigMap (or the keys/values of peerConfigMap) so
resolveSessionHumanPeer and related consumers can access every participant
rather than just lastSenderId.

---

Nitpick comments:
In `@config.ts`:
- Around line 37-45: parseStringRecord currently keeps leading/trailing
whitespace and silently drops malformed entries; update it to trim both key and
value before validating emptiness (use trimmedK/trimmedV for checks and
insertion) and change failure behavior to fail fast on invalid entries (e.g.,
throw an Error or surface a clear log message) instead of silently ignoring
them; update the loop over Object.entries(raw as Record<string, unknown>) and
the result assignment to use the trimmed strings and add a validation branch
that raises an explicit error when encountering non-string values or empty
strings after trimming so misconfigurations are detected early.

In `@hooks/capture.ts`:
- Around line 13-33: getRawContent duplicates the message content extraction
already implemented in extractMessages (helpers.ts); refactor by extracting the
shared logic into a single exported helper (e.g., extractRawTextFromMessage or
normalizeMessageContent) in helpers.ts (or a new utils module), update
getRawContent to call that helper, and replace other similar extraction sites to
import and reuse the new helper; ensure the helper accepts unknown/msg: unknown
and preserves current behavior for string, array-of-blocks, and fallback to ""
so callers like getRawContent and extractMessages remain functionally identical.

In `@state.ts`:
- Around line 113-142: getHumanPeer currently has a race where concurrent calls
for the same new channelPeerId can each miss state.humanPeers and create
duplicate honcho.peer and honcho.setMetadata calls; add a pending-creation
dedupe: introduce a Map<string, Promise<Peer>> (e.g., pendingHumanPeers) checked
immediately after the cache miss, store the in-flight Promise before any await
(so subsequent callers return the same Promise), perform the
honcho.peer/honcho.setMetadata work inside that Promise, and finally remove the
entry from pendingHumanPeers and set state.humanPeers once resolved; keep
existing OWNER_ID short-circuit unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f528c4cc-caf2-45c0-ad4c-f7fb96337522

📥 Commits

Reviewing files that changed from the base of the PR and between 97f4eff and a3f828e.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • README.md
  • commands/cli.ts
  • config.ts
  • helpers.ts
  • hooks/capture.ts
  • hooks/context.ts
  • openclaw.plugin.json
  • state.ts
  • tools/ask.ts
  • tools/context.ts
  • tools/message-search.ts
  • tools/search.ts
  • tools/session.ts

Comment thread commands/cli.ts Outdated
Comment thread hooks/capture.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
hooks/capture.ts (2)

110-115: Consider parallel peer resolution for better performance.

Sequential await calls in the loop become a latency bottleneck in group chats with many unique senders.

♻️ Proposed parallel resolution
-  const resolvedPeers = new Map<string, Awaited<ReturnType<typeof state.getHumanPeer>>>();
-  for (const senderId of senderIds) {
-    resolvedPeers.set(senderId, await state.getHumanPeer(senderId));
-  }
+  const senderIdArray = [...senderIds];
+  const peers = await Promise.all(senderIdArray.map((id) => state.getHumanPeer(id)));
+  const resolvedPeers = new Map(senderIdArray.map((id, i) => [id, peers[i]]));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/capture.ts` around lines 110 - 115, The loop resolving peers
sequentially is a performance bottleneck; instead, concurrently resolve all
peers with Promise.all by mapping senderIds to promises of
state.getHumanPeer(senderId), await them together, and populate resolvedPeers
from the resulting array; also fetch defaultHumanPeer in parallel (e.g., start
its promise before awaiting the batch) so both per-sender and default resolution
happen concurrently while keeping the same Map population logic that uses
resolvedPeers and the state.getHumanPeer symbol.

13-33: Consider reusing raw content extraction from helpers.ts.

This logic duplicates the raw content extraction in extractMessages (helpers.ts lines 225-235). If the extraction rules change, both locations need updating.

Consider exporting a shared helper from helpers.ts that both getRawContent and extractMessages can use.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/capture.ts` around lines 13 - 33, The getRawContent function duplicates
logic already used by extractMessages; extract that shared logic into a single
exported helper (e.g., extractRawContent(msg: unknown): string) in helpers.ts,
update extractMessages to call that helper, and replace getRawContent with a
simple import that delegates to the new helper (or remove getRawContent and
import the helper directly) so both places use the same implementation and
signature (unknown -> string).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hooks/capture.ts`:
- Around line 143-161: The metadata keys humanPeerId and humanPeerIds in
updatedMeta currently store raw sender IDs (derived from senderIds/lastSenderId)
rather than Honcho peer object IDs; either rename these keys to
humanSenderId/humanSenderIds or update the nearby comment to state explicitly
that these values are sender IDs that must be passed to getHumanPeer() for
resolution (adjust references where existingMeta or callers expect
humanPeerId/humanPeerIds to avoid breaking usage). Ensure all references to
humanPeerId/humanPeerIds in this module and any consumers are updated to the new
names or documented behavior so tooling uses getHumanPeer(senderId) to obtain
peer objects.

---

Nitpick comments:
In `@hooks/capture.ts`:
- Around line 110-115: The loop resolving peers sequentially is a performance
bottleneck; instead, concurrently resolve all peers with Promise.all by mapping
senderIds to promises of state.getHumanPeer(senderId), await them together, and
populate resolvedPeers from the resulting array; also fetch defaultHumanPeer in
parallel (e.g., start its promise before awaiting the batch) so both per-sender
and default resolution happen concurrently while keeping the same Map population
logic that uses resolvedPeers and the state.getHumanPeer symbol.
- Around line 13-33: The getRawContent function duplicates logic already used by
extractMessages; extract that shared logic into a single exported helper (e.g.,
extractRawContent(msg: unknown): string) in helpers.ts, update extractMessages
to call that helper, and replace getRawContent with a simple import that
delegates to the new helper (or remove getRawContent and import the helper
directly) so both places use the same implementation and signature (unknown ->
string).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7ff26548-7059-464f-a41a-84c0b8747bb7

📥 Commits

Reviewing files that changed from the base of the PR and between a3f828e and 059ce6b.

📒 Files selected for processing (2)
  • commands/cli.ts
  • hooks/capture.ts
✅ Files skipped from review due to trivial changes (1)
  • commands/cli.ts

Comment thread hooks/capture.ts
Comment on lines +143 to +161
// Store sender IDs in session metadata for tool resolution.
// humanPeerId = last active sender (default for tools).
// humanPeerIds = all known senders in this session (for future multi-target tools).
const previousPeerIds: string[] = Array.isArray(existingMeta.humanPeerIds)
? (existingMeta.humanPeerIds as string[])
: [];
const allPeerIds = [...new Set([...previousPeerIds, ...senderIds])];

const updatedMeta: Record<string, unknown> = {
...existingMeta,
...sessionMeta,
lastSavedIndex: messages.length,
};
if (lastSenderId) {
updatedMeta.humanPeerId = lastSenderId;
}
if (allPeerIds.length > 0) {
updatedMeta.humanPeerIds = allPeerIds;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Improvement: now persists all sender IDs for multi-participant access.

This addresses the previous review concern about only storing the last sender. The dual storage (humanPeerId for default tool resolution, humanPeerIds for future multi-target tools) is a reasonable design.

However, the naming is misleading: these metadata keys store sender IDs (the raw identifiers extracted from messages), not Honcho peer object IDs. Consider renaming to humanSenderId/humanSenderIds to clarify the distinction, or update the comment to explicitly note these are sender IDs that must be passed to getHumanPeer() for resolution.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/capture.ts` around lines 143 - 161, The metadata keys humanPeerId and
humanPeerIds in updatedMeta currently store raw sender IDs (derived from
senderIds/lastSenderId) rather than Honcho peer object IDs; either rename these
keys to humanSenderId/humanSenderIds or update the nearby comment to state
explicitly that these values are sender IDs that must be passed to
getHumanPeer() for resolution (adjust references where existingMeta or callers
expect humanPeerId/humanPeerIds to avoid breaking usage). Ensure all references
to humanPeerId/humanPeerIds in this module and any consumers are updated to the
new names or documented behavior so tooling uses getHumanPeer(senderId) to
obtain peer objects.

Copy link
Copy Markdown
Collaborator

@ajspig ajspig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @spooktheducks! This is solid work. The sender extraction approach is right (parsing sender_id from the metadata block before cleanMessageContent() strips it), and every tool + hook + CLI command is migrated to the new getHumanPeer/resolveSessionHumanPeer pattern. Backward compat is clean.

Blocking:

  1. Add concurrency protection to ensureInitialized() (state.ts). The current bare flag-check (if (state.initialized) return) races when two concurrent hooks enter init simultaneously. This can corrupt workspace metadata. #32 adds an initLock for this — please adopt a similar promise-based lock pattern. Make sure errors propagate to waiters (don't just resolve in finally).

Should fix:

  1. Parallel peer resolution (capture.ts ~110-115). The sequential for...of + await getHumanPeer() loop becomes a latency bottleneck with many senders. Use Promise.all() or Promise.allSettled().

  2. Naming: humanPeerId in session metadata stores sender IDs, not Honcho peer IDs. These raw channel IDs (e.g. U01ZB5DG019) get passed through getHumanPeer() for resolution. Consider humanSenderId/humanSenderIds or a clarifying comment so future readers don't confuse them with resolved Honcho peer IDs.

Note: This PR overlaps significantly with #32 (workspace routing) on state.ts, capture.ts, and context.ts. I'm reviewing both and will decide merge order. Will be heavily dependent on which PR addresses the review first.

menhguin added a commit to menhguin/openclaw-honcho that referenced this pull request Apr 6, 2026
Incorporates the multi-peer mapping approach from plastic-labs#31 (spooktheducks)
with all three review fixes requested by @ajspig:

1. Concurrency protection: ensureInitialized() now uses a promise-based
   init lock to prevent races when two concurrent hooks enter init
   simultaneously. Errors propagate to all waiters (not swallowed).

2. Parallel peer resolution: capture.ts uses Promise.all() instead of
   sequential for...of + await for resolving human peers, avoiding
   latency bottleneck in group chats with many unique senders.

3. Naming clarity: session metadata keys renamed from humanPeerId/
   humanPeerIds to humanSenderId/humanSenderIds to distinguish raw
   channel sender IDs from resolved Honcho peer IDs. Includes backward-
   compatible fallback to legacy key names.

Closes plastic-labs#50. Supersedes plastic-labs#31.
Co-authored-by: spooktheducks <spooktheducks@users.noreply.github.com>

Co-authored-by: Minh Nguyen <menhguin@users.noreply.github.com>
menhguin added a commit to menhguin/openclaw-honcho that referenced this pull request Apr 6, 2026
Incorporates the multi-peer mapping approach from plastic-labs#31 (spooktheducks)
with all three review fixes requested by @ajspig:

1. Concurrency protection: ensureInitialized() now uses a promise-based
   init lock to prevent races when two concurrent hooks enter init
   simultaneously. Errors propagate to all waiters (not swallowed).

2. Parallel peer resolution: capture.ts uses Promise.all() instead of
   sequential for...of + await for resolving human peers, avoiding
   latency bottleneck in group chats with many unique senders.

3. Naming clarity: session metadata keys renamed from humanPeerId/
   humanPeerIds to humanSenderId/humanSenderIds to distinguish raw
   channel sender IDs from resolved Honcho peer IDs. Includes backward-
   compatible fallback to legacy key names.

Closes plastic-labs#50. Supersedes plastic-labs#31.
Co-authored-by: spooktheducks <spooktheducks@users.noreply.github.com>

Co-authored-by: Minh Nguyen <menhguin@users.noreply.github.com>
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.

2 participants