mirror of
https://github.com/block/buzz.git
synced 2026-11-01 15:01:12 +00:00
Keep authored intent in the scoped draft store independently of value deletion. Supersede recovery, sent cleanup and lifecycle persistence across reentry and remount while preserving ordinary destination-bound sends and untouched optimistic recovery. Keep editability and automatic prefix restoration programmatic. Exercise the real store, React lifecycle transition matrix and retained browser editor. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com> (cherry picked from commit ff140562c693147730d548582bc39a12d37ce210) Signed-off-by: Logan Johnson <loganj@squareup.com> (cherry picked from commit 4b99bc705ebb8a6920c114620360433556bf88f9) Signed-off-by: Logan Johnson <loganj@squareup.com>
167 lines
5.4 KiB
TypeScript
167 lines
5.4 KiB
TypeScript
import type { MentionRevalidationOptions } from "@/features/messages/lib/agentMentionRevalidation";
|
|
import type { ManagedAgent } from "@/shared/api/types";
|
|
import {
|
|
type ImetaMedia,
|
|
mergeOutgoingTags,
|
|
} from "@/features/messages/lib/imetaMediaMarkdown";
|
|
import type { QueuedMediaAttachment } from "@/features/messages/lib/backgroundMediaUploadStore";
|
|
import type { PreparedBackgroundLinkPreviews } from "@/features/messages/lib/linkPreviewPreparationStore";
|
|
import type { DraftMentionRef } from "@/features/messages/lib/useDrafts";
|
|
import { normalizePubkey } from "@/shared/lib/pubkey";
|
|
import { MENTION_REFERENCE_TAG } from "@/shared/lib/resolveMentionNames";
|
|
|
|
export { MENTION_REFERENCE_TAG };
|
|
|
|
/** A single visit to a source draft; returning to the same key is a new owner. */
|
|
export type ComposerDraftOwner = {
|
|
channelId: string | null;
|
|
draftKey: string | null | undefined;
|
|
/** Read shared source-key intent, never another visible draft key. */
|
|
getComposerRevision: () => number;
|
|
};
|
|
|
|
export type PendingNonMemberMentionSend = {
|
|
sourceOwner: ComposerDraftOwner;
|
|
composerRevision: number;
|
|
invitationSignal?: AbortSignal;
|
|
addressedAgentPubkeys: string[];
|
|
inlineAgentMentionPubkeys: string[];
|
|
capturedChannelId: string | null;
|
|
capturedThreadContext: {
|
|
parentEventId: string | null;
|
|
threadHeadId: string | null;
|
|
} | null;
|
|
trimmed: string;
|
|
mentionPubkeys: string[];
|
|
nonMemberPubkeys: string[];
|
|
outgoingTags?: string[][];
|
|
preparedLinkPreviews?: PreparedBackgroundLinkPreviews | null;
|
|
preparedManagedAgents?: ManagedAgent[];
|
|
readyAgentPubkeys?: string[];
|
|
savedContent: string;
|
|
savedImeta: ImetaMedia[];
|
|
queuedAttachments: QueuedMediaAttachment[];
|
|
savedSpoileredAttachmentUrls: Set<string>;
|
|
sentDraftKey: string | null | undefined;
|
|
recoveryDraftKey: string | null | undefined;
|
|
savedMentionRefs: DraftMentionRef[];
|
|
};
|
|
|
|
export type SendMessageWithMentionFlowInput = {
|
|
addressedAgentPubkeys?: readonly string[];
|
|
capturedChannelId: string | null;
|
|
capturedThreadContext?: PendingNonMemberMentionSend["capturedThreadContext"];
|
|
pendingImeta: ImetaMedia[];
|
|
queuedAttachments?: QueuedMediaAttachment[];
|
|
linkPreviewTags?: string[][];
|
|
preparedLinkPreviews?: PreparedBackgroundLinkPreviews | null;
|
|
sentDraftKey: string | null | undefined;
|
|
recoveryDraftKey: string | null | undefined;
|
|
spoileredAttachmentUrls?: ReadonlySet<string>;
|
|
trimmed: string;
|
|
};
|
|
|
|
export async function resolvePreviewTags(
|
|
draft: Pick<PendingNonMemberMentionSend, "preparedLinkPreviews">,
|
|
mediaTags: string[][] | undefined,
|
|
outgoingTags: string[][] | undefined,
|
|
): Promise<string[][] | null> {
|
|
const result = await draft.preparedLinkPreviews?.promise;
|
|
if (result?.status === "cancelled") return null;
|
|
return (
|
|
mergeOutgoingTags(mediaTags, [
|
|
...(outgoingTags ?? []),
|
|
...(result?.tags ?? []),
|
|
]) ?? []
|
|
);
|
|
}
|
|
|
|
export function mergeOutgoingTagsWithReferenceMentions(
|
|
outgoingTags: string[][] | undefined,
|
|
pubkeys: Iterable<string>,
|
|
) {
|
|
const normalizedPubkeys = uniqueNormalizedPubkeys(pubkeys);
|
|
if (normalizedPubkeys.length === 0) {
|
|
return outgoingTags;
|
|
}
|
|
|
|
return [
|
|
...(outgoingTags ?? []),
|
|
...normalizedPubkeys.map((pubkey) => [MENTION_REFERENCE_TAG, pubkey]),
|
|
];
|
|
}
|
|
|
|
export function getErrorMessage(error: unknown, fallback: string) {
|
|
if (error instanceof Error && error.message) return error.message;
|
|
if (typeof error === "string" && error.trim()) return error;
|
|
if (
|
|
typeof error === "object" &&
|
|
error !== null &&
|
|
"message" in error &&
|
|
typeof error.message === "string" &&
|
|
error.message.trim()
|
|
) {
|
|
return error.message;
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
export function formatMessageSendError(error: unknown) {
|
|
return `Message failed to send: ${getErrorMessage(error, "Unknown error")}`;
|
|
}
|
|
|
|
export function uniqueNormalizedPubkeys(pubkeys: Iterable<string>) {
|
|
return [...new Set([...pubkeys].map(normalizePubkey))].filter(Boolean);
|
|
}
|
|
|
|
export function mergeMentionRecipients(
|
|
explicitMentionPubkeys: Iterable<string>,
|
|
addressedAgentPubkeys: Iterable<string>,
|
|
) {
|
|
return uniqueNormalizedPubkeys([
|
|
...explicitMentionPubkeys,
|
|
...addressedAgentPubkeys,
|
|
]);
|
|
}
|
|
|
|
export function isManagedAgentRunning(agent: ManagedAgent) {
|
|
return agent.status === "running" || agent.status === "deployed";
|
|
}
|
|
|
|
export function isProviderBackedAgent(agent: ManagedAgent) {
|
|
return agent.backend.type === "provider";
|
|
}
|
|
|
|
/** Carry captured recipient identity through composer clearing and uploads. */
|
|
export function mentionRevalidationOptions(
|
|
draft: Pick<
|
|
PendingNonMemberMentionSend,
|
|
"inlineAgentMentionPubkeys" | "addressedAgentPubkeys"
|
|
>,
|
|
phase: "prepare" | "publish",
|
|
preparedAgentPubkeys: readonly string[] = [],
|
|
): MentionRevalidationOptions {
|
|
return {
|
|
phase,
|
|
intendedAgentPubkeys: uniqueNormalizedPubkeys([
|
|
...draft.inlineAgentMentionPubkeys,
|
|
...draft.addressedAgentPubkeys,
|
|
...preparedAgentPubkeys,
|
|
]),
|
|
};
|
|
}
|
|
|
|
/** Explicit Send without inviting retains nonmembers only as reference tags. */
|
|
export function withoutInvitingRecipients(draft: PendingNonMemberMentionSend) {
|
|
const nonMemberPubkeys = new Set(draft.nonMemberPubkeys.map(normalizePubkey));
|
|
return {
|
|
mentionPubkeys: draft.mentionPubkeys.filter(
|
|
(pubkey) => !nonMemberPubkeys.has(normalizePubkey(pubkey)),
|
|
),
|
|
outgoingTags: mergeOutgoingTagsWithReferenceMentions(
|
|
draft.outgoingTags,
|
|
nonMemberPubkeys,
|
|
),
|
|
};
|
|
}
|