Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
118 lines
3.5 KiB
TypeScript
118 lines
3.5 KiB
TypeScript
import type { UiLanguage } from "../../contexts/settings/domain/agentSettings";
|
|
import { normalizeAgentTitleText } from "./agentTitleText.ts";
|
|
import {
|
|
isRichTextMentionHref,
|
|
parseRichTextMentionHref
|
|
} from "@tutti-os/ui-rich-text/core";
|
|
|
|
const MARKDOWN_LINK_PATTERN = /\[((?:\\.|[^\]\\])*)\]\(([^)\s]+)\)/g;
|
|
const MARKDOWN_LABEL_ESCAPE_PATTERN = /\\([\\[\]()])/g;
|
|
const SESSION_MENTION_DISPLAY_PREFIX_BY_LANGUAGE: Record<UiLanguage, string> = {
|
|
"zh-CN": "@会话 · ",
|
|
en: "@session · "
|
|
};
|
|
const PLAIN_SESSION_AGENT_LABELS = [
|
|
"Claude Code",
|
|
"Hermes Agent",
|
|
"OpenClaw",
|
|
"Nexight",
|
|
"Hermes",
|
|
"Codex"
|
|
] as const;
|
|
|
|
export interface FormatAgentSessionMentionTextOptions {
|
|
language?: UiLanguage;
|
|
}
|
|
|
|
export function formatAgentSessionMentionText(
|
|
value: string | null | undefined,
|
|
options: FormatAgentSessionMentionTextOptions = {}
|
|
): string {
|
|
const trimmed = value?.trim() ?? "";
|
|
if (!trimmed) {
|
|
return "";
|
|
}
|
|
const sessionMentionDisplayPrefix = sessionMentionDisplayPrefixForLanguage(
|
|
options.language
|
|
);
|
|
|
|
const withSessionMentionsNormalized = trimmed.replace(
|
|
MARKDOWN_LINK_PATTERN,
|
|
(fullMatch, rawLabel: string, href: string) => {
|
|
const label = unescapeMarkdownLabel(rawLabel);
|
|
const mention = parseRichTextMentionHref(href, label);
|
|
if (!mention) {
|
|
return isRichTextMentionHref(href) ? label : fullMatch;
|
|
}
|
|
const providerId = mention.providerId.trim().toLowerCase();
|
|
const displayLabel = label || mention.label;
|
|
if (providerId === "agent-session") {
|
|
return formatSessionLabel(displayLabel, sessionMentionDisplayPrefix);
|
|
}
|
|
if (providerId === "workspace-issue") {
|
|
return formatIssueLabel(displayLabel);
|
|
}
|
|
return displayLabel;
|
|
}
|
|
);
|
|
const normalized = normalizeAgentTitleText(withSessionMentionsNormalized);
|
|
if (isPlainSessionMentionText(normalized, sessionMentionDisplayPrefix)) {
|
|
return formatSessionLabel(normalized, sessionMentionDisplayPrefix);
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
function isPlainSessionMentionText(
|
|
value: string,
|
|
sessionMentionDisplayPrefix: string
|
|
): boolean {
|
|
const trimmed = value.trim();
|
|
if (
|
|
!trimmed.startsWith("@") ||
|
|
trimmed.startsWith(sessionMentionDisplayPrefix)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return PLAIN_SESSION_AGENT_LABELS.some((agentLabel) => {
|
|
const ampersandSeparator = ` & ${agentLabel}`;
|
|
const ampersandIndex = trimmed.indexOf(ampersandSeparator);
|
|
if (ampersandIndex > 1) {
|
|
const trailing = trimmed.slice(
|
|
ampersandIndex + ampersandSeparator.length
|
|
);
|
|
if (trailing === "" || trailing.startsWith(" ")) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const dottedSeparator = ` · ${agentLabel} · `;
|
|
return trimmed.indexOf(dottedSeparator) > 1;
|
|
});
|
|
}
|
|
|
|
function sessionMentionDisplayPrefixForLanguage(
|
|
language: UiLanguage | undefined
|
|
): string {
|
|
return SESSION_MENTION_DISPLAY_PREFIX_BY_LANGUAGE[language ?? "en"];
|
|
}
|
|
|
|
function formatSessionLabel(
|
|
label: string,
|
|
sessionMentionDisplayPrefix: string
|
|
): string {
|
|
const normalizedLabel = label.trim().replace(/^@+/, "").trim();
|
|
return normalizedLabel
|
|
? `${sessionMentionDisplayPrefix}${normalizedLabel}`
|
|
: label.trim();
|
|
}
|
|
|
|
function formatIssueLabel(label: string): string {
|
|
const normalizedLabel = label.trim().replace(/^@+/, "").trim();
|
|
return normalizedLabel ? `@Task · ${normalizedLabel}` : label.trim();
|
|
}
|
|
|
|
function unescapeMarkdownLabel(label: string): string {
|
|
return label.replace(MARKDOWN_LABEL_ESCAPE_PATTERN, "$1");
|
|
}
|