- Move all v1 files (index, router, container-runner, db, ipc, types, logger, channels/registry, and all utilities) to src/v1/ as a fully self-contained archive with no shared dependencies - Rename v2 files to remove -v2 suffix (index-v2.ts → index.ts, etc.) - Update all imports across v2 source, tests, and setup files - Migrate shared utilities (config, env, container-runtime, mount-security, timezone, group-folder) from pino logger to v2 log module - Migrate setup/ files from logger to log with argument order swap - Container agent-runner: move v1 entry to v1/, rename v2 to index.ts - Update setup skill to offer all 13 v2 channels - Install all Chat SDK adapter packages - dist/index.js now runs v2; dist/v1/index.js runs v1 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
44 lines
1.7 KiB
TypeScript
44 lines
1.7 KiB
TypeScript
import { Channel, NewMessage } from './types.js';
|
|
import { formatLocalTime } from './timezone.js';
|
|
|
|
export function escapeXml(s: string): string {
|
|
if (!s) return '';
|
|
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
export function formatMessages(messages: NewMessage[], timezone: string): string {
|
|
const lines = messages.map((m) => {
|
|
const displayTime = formatLocalTime(m.timestamp, timezone);
|
|
const replyAttr = m.reply_to_message_id ? ` reply_to="${escapeXml(m.reply_to_message_id)}"` : '';
|
|
const replySnippet =
|
|
m.reply_to_message_content && m.reply_to_sender_name
|
|
? `\n <quoted_message from="${escapeXml(m.reply_to_sender_name)}">${escapeXml(m.reply_to_message_content)}</quoted_message>`
|
|
: '';
|
|
return `<message sender="${escapeXml(m.sender_name)}" time="${escapeXml(displayTime)}"${replyAttr}>${replySnippet}${escapeXml(m.content)}</message>`;
|
|
});
|
|
|
|
const header = `<context timezone="${escapeXml(timezone)}" />\n`;
|
|
|
|
return `${header}<messages>\n${lines.join('\n')}\n</messages>`;
|
|
}
|
|
|
|
export function stripInternalTags(text: string): string {
|
|
return text.replace(/<internal>[\s\S]*?<\/internal>/g, '').trim();
|
|
}
|
|
|
|
export function formatOutbound(rawText: string): string {
|
|
const text = stripInternalTags(rawText);
|
|
if (!text) return '';
|
|
return text;
|
|
}
|
|
|
|
export function routeOutbound(channels: Channel[], jid: string, text: string): Promise<void> {
|
|
const channel = channels.find((c) => c.ownsJid(jid) && c.isConnected());
|
|
if (!channel) throw new Error(`No channel for JID: ${jid}`);
|
|
return channel.sendMessage(jid, text);
|
|
}
|
|
|
|
export function findChannel(channels: Channel[], jid: string): Channel | undefined {
|
|
return channels.find((c) => c.ownsJid(jid));
|
|
}
|