Ship DM connect delivery, fleet pubkey lookup, OpenClaw Infonet agent, and relay auto-wormhole.

Auto-relay connect DMs with End Contact severing, signed fleet prekey lookup,
OpenClaw private Infonet channel intents, headless relay Tor bootstrap on redeploy,
and swarm/DM live verification scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
BigBodyCobain
2026-06-12 02:15:56 -06:00
parent d48a0cdace
commit 89d6bb8fb9
52 changed files with 4211 additions and 339 deletions
@@ -682,6 +682,20 @@ export default function InfonetShell({
{/* Main Terminal Area */}
<div className="flex-1 overflow-y-auto pr-4 pb-4">
<button
type="button"
onClick={() => handleNavigate('messages')}
className="w-full mb-6 text-left border border-emerald-500/30 bg-emerald-950/10 hover:bg-emerald-950/20 px-4 py-3 transition-colors"
>
<div className="flex items-center gap-2 text-emerald-300 text-xs tracking-[0.2em] uppercase font-bold">
<Mail size={14} />
Secure Messages Quick Connect
</div>
<p className="mt-2 text-sm text-gray-400 leading-relaxed">
Message someone on the fleet in three steps: copy your short address (or ask for theirs),
paste it in Secure Messages, tap Send Request they tap Accept. No terminal commands.
</p>
</button>
<div className="flex flex-col lg:flex-row justify-between items-start gap-6 mb-8">
<TrendingPosts />
@@ -66,6 +66,7 @@ import {
purgeBrowserContactGraph,
purgeBrowserSigningMaterial,
removeContact,
severContact,
unblockContact,
unwrapSenderSealPayload,
updateContact,
@@ -74,6 +75,7 @@ import {
type Contact,
type NodeIdentity,
} from '@/mesh/meshIdentity';
import { connectDeliveryMeta, ensureDmOutboxReleased } from '@/mesh/dmConnectDelivery';
import {
getSenderRecoveryState,
recoverSenderSealWithFallback,
@@ -1516,6 +1518,7 @@ export default function MessagesView({ onBack, onOpenDeadDrop }: MessagesViewPro
API_BASE,
senderId,
existingContact?.invitePinnedPrekeyLookupHandle,
{ lookupPeerUrl: existingContact?.invitePinnedLookupPeerUrl },
);
if (senderKey?.dh_pub_key) {
const sharedKey = await deriveSharedKey(String(senderKey.dh_pub_key));
@@ -1532,6 +1535,7 @@ export default function MessagesView({ onBack, onOpenDeadDrop }: MessagesViewPro
API_BASE,
senderId,
existingContact?.invitePinnedPrekeyLookupHandle,
{ lookupPeerUrl: existingContact?.invitePinnedLookupPeerUrl },
).catch(() => null);
if (senderKey?.dh_pub_key) {
addContact(senderId, String(senderKey.dh_pub_key), undefined, senderKey.dh_algo);
@@ -2000,7 +2004,9 @@ export default function MessagesView({ onBack, onOpenDeadDrop }: MessagesViewPro
'This contact needs their full contact address once before messages can be sent. Paste it in Contacts and the app will handle the rest.',
);
}
const targetKey = await fetchDmPublicKey(API_BASE, recipient, lookupHandle);
const targetKey = await fetchDmPublicKey(API_BASE, recipient, lookupHandle, {
lookupPeerUrl: recipientContact?.invitePinnedLookupPeerUrl,
});
if (!targetKey?.dh_pub_key) {
queuePendingDeliveryMail({
senderId: activeIdentity.nodeId,
@@ -2037,15 +2043,23 @@ export default function MessagesView({ onBack, onOpenDeadDrop }: MessagesViewPro
}
const msgId = `dm_${Date.now()}_${activeIdentity.nodeId.slice(-4)}`;
const timestamp = Math.floor(Date.now() / 1000);
const sent = await sendOffLedgerConsentMessage({
apiBase: API_BASE,
identity: activeIdentity,
recipientId: recipient,
recipientDhPub: String(targetKey.dh_pub_key),
ciphertext,
msgId,
timestamp,
const connectMeta = connectDeliveryMeta({
intent: 'contact_request',
contact: recipientContact,
});
const sent = await ensureDmOutboxReleased(
await sendOffLedgerConsentMessage({
apiBase: API_BASE,
identity: activeIdentity,
recipientId: recipient,
recipientDhPub: String(targetKey.dh_pub_key),
ciphertext,
msgId,
timestamp,
connectIntent: connectMeta.connectIntent,
lookupPeerUrl: connectMeta.lookupPeerUrl,
}),
);
if (!sent.ok) {
throw new Error(sent.detail || 'contact request failed');
}
@@ -2110,7 +2124,9 @@ export default function MessagesView({ onBack, onOpenDeadDrop }: MessagesViewPro
throw new Error('Secure mail is still preparing your private identity.');
}
const { registration, myDhPub } = await ensureLocalDmKey(activeIdentity);
const targetKey = await fetchDmPublicKey(API_BASE, '', shortAddress);
const targetKey = await fetchDmPublicKey(API_BASE, '', shortAddress, {
allowLegacyAgentId: false,
});
if (!targetKey?.dh_pub_key || !targetKey.agent_id) {
throw new Error('That address is not reachable yet. Ask them to copy their address again while their device is online.');
}
@@ -2136,15 +2152,18 @@ export default function MessagesView({ onBack, onOpenDeadDrop }: MessagesViewPro
}
const msgId = `dm_${Date.now()}_${activeIdentity.nodeId.slice(-4)}`;
const timestamp = Math.floor(Date.now() / 1000);
const sent = await sendOffLedgerConsentMessage({
apiBase: API_BASE,
identity: activeIdentity,
recipientId: recipient,
recipientDhPub: String(targetKey.dh_pub_key),
ciphertext,
msgId,
timestamp,
});
const sent = await ensureDmOutboxReleased(
await sendOffLedgerConsentMessage({
apiBase: API_BASE,
identity: activeIdentity,
recipientId: recipient,
recipientDhPub: String(targetKey.dh_pub_key),
ciphertext,
msgId,
timestamp,
connectIntent: 'invite_short_address',
}),
);
if (!sent.ok) {
throw new Error(sent.detail || 'contact request failed');
}
@@ -2224,6 +2243,12 @@ export default function MessagesView({ onBack, onOpenDeadDrop }: MessagesViewPro
invitePayload.prekey_lookup_handle ||
'',
),
invitePinnedLookupPeerUrl: String(
resultContact.invitePinnedLookupPeerUrl ||
(invite as Record<string, unknown>).lookup_peer_url ||
invitePayload.lookup_peer_url ||
'',
),
dhPubKey: String(resultContact.dhPubKey || resultContact.invitePinnedDhPubKey || ''),
};
const mergedContacts = importedPeerId
@@ -2269,6 +2294,26 @@ export default function MessagesView({ onBack, onOpenDeadDrop }: MessagesViewPro
}
}, [applyHydratedContacts, handleSendShortAddressRequest, inviteImportAlias, inviteImportBlob, loadBackendContacts, syncSecureMailRuntime]);
const handleSeverContact = useCallback(
async (peerId: string) => {
const name = displayNameForPeer(peerId, contacts);
setComposeError('');
setComposeStatus('');
try {
await severContact(peerId);
setContacts(getContacts());
setComposeStatus(
`Secure contact ended with ${name}. You can message again only after a new request and approval.`,
);
} catch (error) {
setComposeError(
error instanceof Error ? error.message : 'Could not end secure contact right now.',
);
}
},
[contacts],
);
const refreshDmAddressHandles = useCallback(async () => {
try {
const result = await listWormholeDmInviteHandles();
@@ -2501,6 +2546,7 @@ export default function MessagesView({ onBack, onOpenDeadDrop }: MessagesViewPro
API_BASE,
mail.senderId,
existingContact?.invitePinnedPrekeyLookupHandle,
{ lookupPeerUrl: existingContact?.invitePinnedLookupPeerUrl },
).catch(() => null);
const dhPubKey = String(registry?.dh_pub_key || mail.requestDhPubKey || '').trim();
const dhAlgo = String(registry?.dh_algo || mail.requestDhAlgo || 'X25519').trim();
@@ -2551,15 +2597,19 @@ export default function MessagesView({ onBack, onOpenDeadDrop }: MessagesViewPro
const msgId = `dm_${Date.now()}_${activeIdentity.nodeId.slice(-4)}`;
const timestamp = Math.floor(Date.now() / 1000);
const sent = await sendOffLedgerConsentMessage({
apiBase: API_BASE,
identity: activeIdentity,
recipientId: mail.senderId,
recipientDhPub: dhPubKey,
ciphertext,
msgId,
timestamp,
});
const sent = await ensureDmOutboxReleased(
await sendOffLedgerConsentMessage({
apiBase: API_BASE,
identity: activeIdentity,
recipientId: mail.senderId,
recipientDhPub: dhPubKey,
ciphertext,
msgId,
timestamp,
connectIntent: 'contact_accept',
lookupPeerUrl: existingContact?.invitePinnedLookupPeerUrl,
}),
);
if (!sent.ok) {
throw new Error(sent.detail || 'contact accept failed');
}
@@ -2715,7 +2765,9 @@ export default function MessagesView({ onBack, onOpenDeadDrop }: MessagesViewPro
<Mail size={24} className="mr-3" />
SECURE MESSAGES
</h1>
<p className="text-gray-500 text-sm mt-1">End-to-end encrypted peer-to-peer comms.</p>
<p className="text-gray-500 text-sm mt-1">
Copy your short address and send it to someone. They paste it here and tap Send Request you tap Accept. No terminal required.
</p>
</div>
<div className="border border-cyan-900/30 bg-cyan-950/10 px-4 py-3 text-[11px] tracking-[0.16em] uppercase text-cyan-300 mb-4 shrink-0">
@@ -2755,7 +2807,7 @@ export default function MessagesView({ onBack, onOpenDeadDrop }: MessagesViewPro
</div>
</>
) : (
'Your contact address is being prepared automatically. Share it with someone so they can message you.'
'Your contact address is being prepared. Copy the short address above and send it to anyone you want to message you.'
)}
</div>
</div>
@@ -3428,7 +3480,7 @@ export default function MessagesView({ onBack, onOpenDeadDrop }: MessagesViewPro
)}
{contact.sharedAlias && (
<div className="text-[11px] text-emerald-300 mt-2">
Shared alias: {contact.sharedAlias}
Shared lane open you can exchange secure mail.
</div>
)}
</div>
@@ -3466,6 +3518,18 @@ export default function MessagesView({ onBack, onOpenDeadDrop }: MessagesViewPro
{nextStep.label}
</button>
)}
{contact.sharedAlias && (
<button
onClick={() => void handleSeverContact(peerId)}
className="px-3 py-2 border border-violet-500/30 text-violet-200 text-sm tracking-[0.18em] uppercase"
title="Close the shared lane. A fresh contact request and approval will be required to message again."
>
<span className="inline-flex items-center gap-1.5">
<ShieldOff size={14} />
End Contact
</span>
</button>
)}
<button
onClick={() => {
blockContact(peerId);
+3 -9
View File
@@ -361,15 +361,9 @@ const MeshChat = React.memo(function MeshChat(props: MeshChatProps) {
setShowSas((prev) => !prev);
};
const handleRequestComposerAction = () => {
const targetId = addContactId.trim();
if (!targetId) return;
const inviteLookupHandle = String(
contacts[targetId]?.invitePinnedPrekeyLookupHandle || '',
).trim();
if (!inviteLookupHandle) {
openTerminal();
}
void handleRequestAccess(targetId);
const pasted = addContactId.trim();
if (!pasted) return;
void handleRequestAccess(pasted);
};
const meshActivationText =
publicMeshBlockedByWormhole
@@ -27,6 +27,7 @@ import {
addContact,
updateContact,
blockContact,
severContact,
getDMNotify,
nextSequence,
verifyEventSignature,
@@ -103,6 +104,7 @@ import {
isEncryptedGateEnvelope,
} from '@/mesh/gateEnvelope';
import { fetchWormholeSettings, joinWormhole, leaveWormhole } from '@/mesh/wormholeClient';
import { connectDeliveryMeta, ensureDmOutboxReleased } from '@/mesh/dmConnectDelivery';
import {
buildMailboxClaims,
countDmMailboxes,
@@ -2295,6 +2297,7 @@ export function useMeshChatController({
API_BASE,
m.sender_id,
senderContact?.invitePinnedPrekeyLookupHandle,
{ lookupPeerUrl: senderContact?.invitePinnedLookupPeerUrl },
);
if (senderKey?.dh_pub_key) {
const sharedKey = await deriveSharedKey(String(senderKey.dh_pub_key));
@@ -2310,6 +2313,7 @@ export function useMeshChatController({
API_BASE,
m.sender_id,
senderContact?.invitePinnedPrekeyLookupHandle,
{ lookupPeerUrl: senderContact?.invitePinnedLookupPeerUrl },
).catch(() => null);
if (senderKey?.dh_pub_key) {
addContact(m.sender_id, String(senderKey.dh_pub_key), undefined, senderKey.dh_algo);
@@ -3336,7 +3340,9 @@ export function useMeshChatController({
'import or re-import a signed invite before refreshing this contact; legacy direct lookup is disabled',
);
}
const registry = await fetchDmPublicKey(API_BASE, targetId, lookupHandle).catch(() => null);
const registry = await fetchDmPublicKey(API_BASE, targetId, lookupHandle, {
lookupPeerUrl: existing?.invitePinnedLookupPeerUrl,
}).catch(() => null);
if (!registry?.dh_pub_key) {
throw new Error(
'invite-scoped lookup failed for this contact; re-import a signed invite and try again',
@@ -3585,29 +3591,26 @@ export function useMeshChatController({
setTimeout(() => setSendError(''), 3000);
return;
}
if (requiresVerifiedFirstContact(getContacts()[targetId])) {
setSendError('import a signed invite before first secure contact; TOFU requests are disabled');
setTimeout(() => setSendError(''), 4000);
return;
}
if (wormholeEnabled && !wormholeReadyState) {
setSendError('wormhole required for dead drop');
setTimeout(() => setSendError(''), 3000);
return;
}
try {
const registration = await ensureRegisteredDmKey(API_BASE, identity!, { force: false });
const myPub = registration.dhPubKey;
if (!myPub) return;
const dhAlgo = registration.dhAlgo || getDHAlgo() || 'X25519';
const targetContact = getContacts()[targetId];
const lookupHandle = String(targetContact?.invitePinnedPrekeyLookupHandle || '').trim();
let lookupHandle = String(targetContact?.invitePinnedPrekeyLookupHandle || '').trim();
let resolvedTargetId = targetId;
if (!lookupHandle && /^[a-fA-F0-9]{32,}$/.test(targetId)) {
lookupHandle = targetId;
resolvedTargetId = '';
}
if (!lookupHandle) {
throw new Error(
'import or re-import a signed invite before sending a contact request; legacy direct lookup is disabled',
'Paste their short contact address (from Secure Messages → Copy Short Address), not their node id.',
);
}
const targetKey = await fetchDmPublicKey(API_BASE, targetId, lookupHandle);
const targetKey = await fetchDmPublicKey(API_BASE, resolvedTargetId, lookupHandle, {
lookupPeerUrl: targetContact?.invitePinnedLookupPeerUrl,
});
if (!targetKey?.dh_pub_key) {
throw new Error(
'invite-scoped lookup failed for this contact; re-import a signed invite and try again',
@@ -3631,12 +3634,13 @@ export function useMeshChatController({
geoHint = '';
}
}
const recipientId = String(targetKey.agent_id || resolvedTargetId || targetId).trim();
const requestPlaintext = buildContactOfferMessage(myPub, dhAlgo, geoHint || undefined);
let ciphertext = '';
const secureRequired = await isWormholeSecureRequired();
if (await canUseWormholeBootstrap()) {
try {
ciphertext = await bootstrapEncryptAccessRequest(targetId, requestPlaintext);
ciphertext = await bootstrapEncryptAccessRequest(recipientId, requestPlaintext);
} catch {
ciphertext = '';
}
@@ -3651,16 +3655,24 @@ export function useMeshChatController({
const msgId = `dm_${Date.now()}_${identity!.nodeId.slice(-4)}`;
const msgTimestamp = Math.floor(Date.now() / 1000);
await sleep(jitterDelay(ACCESS_REQUEST_BATCH_DELAY_MS, ACCESS_REQUEST_BATCH_JITTER_MS));
const connectMeta = connectDeliveryMeta({
intent: lookupHandle === targetId ? 'invite_short_address' : 'contact_request',
contact: targetContact,
});
await enqueueDmSend(async () => {
const sent = await sendOffLedgerConsentMessage({
apiBase: API_BASE,
identity: identity!,
recipientId: targetId,
recipientDhPub: String(targetKey.dh_pub_key),
ciphertext,
msgId,
timestamp: msgTimestamp,
});
const sent = await ensureDmOutboxReleased(
await sendOffLedgerConsentMessage({
apiBase: API_BASE,
identity: identity!,
recipientId,
recipientDhPub: String(targetKey.dh_pub_key),
ciphertext,
msgId,
timestamp: msgTimestamp,
connectIntent: connectMeta.connectIntent,
lookupPeerUrl: connectMeta.lookupPeerUrl,
}),
);
if (!sent.ok) {
throw new Error(sent.detail || 'access_request_send_failed');
}
@@ -3668,7 +3680,8 @@ export function useMeshChatController({
setLastDmTransport(sent.transport);
}
});
const updated = [...pendingSent, targetId];
const recipientForPending = String(targetKey.agent_id || resolvedTargetId || targetId).trim();
const updated = [...pendingSent, recipientForPending];
setPendingSent(updated, dmConsentScopeId);
setPendingSentState(updated);
} catch (err) {
@@ -3680,11 +3693,6 @@ export function useMeshChatController({
const handleAcceptRequest = async (senderId: string) => {
if (!hasId) return;
if (requiresVerifiedFirstContact(getContacts()[senderId])) {
setSendError('import a signed invite before accepting an unverified request');
setTimeout(() => setSendError(''), 4000);
return;
}
if (anonymousDmBlocked) {
setSendError('hidden transport required for anonymous dm');
setTimeout(() => setSendError(''), 3000);
@@ -3697,6 +3705,7 @@ export function useMeshChatController({
API_BASE,
senderId,
existingContact?.invitePinnedPrekeyLookupHandle,
{ lookupPeerUrl: existingContact?.invitePinnedLookupPeerUrl },
).catch(() => null);
const resolvedDhPubKey = String(registry?.dh_pub_key || req?.dh_pub_key || '').trim();
const resolvedDhAlgo = String(registry?.dh_algo || req?.dh_algo || 'X25519').trim();
@@ -3843,16 +3852,24 @@ export function useMeshChatController({
}
const msgId = `dm_${Date.now()}_${identity!.nodeId.slice(-4)}`;
const msgTimestamp = Math.floor(Date.now() / 1000);
const acceptMeta = connectDeliveryMeta({
intent: 'contact_accept',
contact: existingContact,
});
await enqueueDmSend(async () => {
const sent = await sendOffLedgerConsentMessage({
apiBase: API_BASE,
identity: identity!,
recipientId: senderId,
recipientDhPub: resolvedDhPubKey,
ciphertext,
msgId,
timestamp: msgTimestamp,
});
const sent = await ensureDmOutboxReleased(
await sendOffLedgerConsentMessage({
apiBase: API_BASE,
identity: identity!,
recipientId: senderId,
recipientDhPub: resolvedDhPubKey,
ciphertext,
msgId,
timestamp: msgTimestamp,
connectIntent: acceptMeta.connectIntent,
lookupPeerUrl: acceptMeta.lookupPeerUrl,
}),
);
if (!sent.ok) {
throw new Error(sent.detail || 'access_granted_send_failed');
}
@@ -3878,11 +3895,6 @@ export function useMeshChatController({
const handleDenyRequest = (senderId: string) => {
void (async () => {
if (requiresVerifiedFirstContact(getContacts()[senderId])) {
setSendError('import a signed invite before denying an unverified request');
setTimeout(() => setSendError(''), 4000);
return;
}
try {
const req = accessRequests.find((r) => r.sender_id === senderId);
const existingContact = getContacts()[senderId];
@@ -3893,6 +3905,7 @@ export function useMeshChatController({
API_BASE,
senderId,
existingContact?.invitePinnedPrekeyLookupHandle,
{ lookupPeerUrl: existingContact?.invitePinnedLookupPeerUrl },
).catch(() => null);
if (identity && targetKey?.dh_pub_key) {
const denyPlaintext = buildContactDenyMessage('declined');
@@ -3935,6 +3948,20 @@ export function useMeshChatController({
})();
};
const handleSeverContact = async (agentId: string) => {
try {
await severContact(agentId);
setContacts(getContacts());
if (selectedContact === agentId) {
setDmView('contacts');
}
} catch (err) {
const detail = err instanceof Error ? err.message : 'end contact failed';
setSendError(detail);
setTimeout(() => setSendError(''), 4000);
}
};
const handleBlockDM = async (agentId: string) => {
blockContact(agentId);
setContacts(getContacts());
@@ -4751,6 +4778,7 @@ export function useMeshChatController({
handleAcceptRequest,
handleDenyRequest,
handleBlockDM,
handleSeverContact,
handleVouch,
handleAddContact,
openChat,