import { stripVTControlCharacters } from "node:util"; import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent"; import type { Component } from "@earendil-works/pi-tui"; import { tuiT } from "./tui-i18n.ts"; import { toolResultCard } from "./quiet-tools.ts"; export const TEAMMATE_MESSAGE_CUSTOM_TYPE = "teammate-message"; interface TeammateMessageDetails { source?: unknown; provenance?: unknown; } interface TeammateMessageEnvelope { kind?: string; sender: string; guidance: string; body: string; } type MessageContent = string | Array<{ type: string; text?: string }>; function cleanMultiline(value: string): string { return stripVTControlCharacters(value) .replace(/\r\n?/g, "\n") .replace(/\t/g, " ") .replace(/[\u0000-\u0009\u000b-\u001f\u007f-\u009f]/g, "") .trimEnd(); } function contentText(content: MessageContent): string { if (typeof content === "string") return cleanMultiline(content); return cleanMultiline(content .filter((entry) => entry.type === "text" && typeof entry.text === "string") .map((entry) => entry.text) .join("\n")); } function isRecord(value: unknown): value is Record { return value !== null && typeof value === "object" && !Array.isArray(value); } function provenanceHeader(details: TeammateMessageDetails | undefined): { sender?: string; kind?: string } { if (!isRecord(details?.provenance)) return {}; const provenance = details.provenance; const sender = isRecord(provenance.sender) && typeof provenance.sender.label === "string" ? provenance.sender.label.trim() : typeof provenance.legacyLabel === "string" ? provenance.legacyLabel.trim() : undefined; const kind = typeof provenance.messageKind === "string" ? provenance.messageKind.trim() : undefined; return { ...(sender ? { sender } : {}), ...(kind ? { kind } : {}), }; } export function parseTeammateMessageEnvelope( content: MessageContent, details?: TeammateMessageDetails, ): TeammateMessageEnvelope { const text = contentText(content); const lines = text.split("\n"); const header = lines[0]?.match(/^\[(?:teammate|workspace):([^\]]+)\]\s+from\s+(.+)$/); const fallback = provenanceHeader(details); const sender = header?.[2]?.trim() || fallback.sender || "teammate"; const kind = header?.[1]?.trim() || fallback.kind; const payloadLines = header ? lines.slice(1) : lines; const separator = payloadLines.findIndex((line) => line.trim() === "---"); if (separator < 0) { return { sender, ...(kind ? { kind } : {}), guidance: "", body: payloadLines.join("\n").trim() }; } return { sender, ...(kind ? { kind } : {}), guidance: payloadLines.slice(0, separator).join("\n").trim(), body: payloadLines.slice(separator + 1).join("\n").trim(), }; } const KIND_KEYS: Readonly> = { message: "message.kind.message", coordination: "message.kind.coordination", request: "message.kind.request", status: "message.kind.status", supervision: "message.kind.supervision", }; function kindLabel(kind: string | undefined): string | undefined { if (!kind) return undefined; const key = KIND_KEYS[kind]; return key ? tuiT(key) : kind; } export function renderIncomingTeammateMessage( message: { content: MessageContent; details?: TeammateMessageDetails }, options: { expanded: boolean; outputPad: number }, theme: Theme, ): Component { const envelope = parseTeammateMessageEnvelope(message.content, message.details); const kind = kindLabel(envelope.kind); const groups: string[][] = []; if (options.expanded && envelope.guidance) { groups.push(envelope.guidance.split("\n").map((line) => theme.fg("dim", line))); } const bodySource = envelope.body || (!options.expanded ? envelope.guidance : ""); if (bodySource) groups.push(bodySource.split("\n")); return toolResultCard(theme, { name: tuiT("message.receivedFrom", { sender: envelope.sender }), mark: theme.fg("accent", "←"), ...(kind ? { summary: kind } : {}), groups, maxBodyRows: options.expanded ? undefined : 8, }); } export function registerTeammateMessageRenderer( pi: Pick, isEnabled: () => boolean = () => true, ): void { pi.registerMessageRenderer( TEAMMATE_MESSAGE_CUSTOM_TYPE, (message, options, theme) => isEnabled() ? renderIncomingTeammateMessage(message, options, theme) : undefined, ); }