v2: make v2 the main entry point, move v1 to src/v1/

- 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>
This commit is contained in:
gavrielc
2026-04-09 11:40:36 +03:00
parent 12af451069
commit 9486d56b01
96 changed files with 7904 additions and 3040 deletions

View File

@@ -1,43 +1,111 @@
import { Channel, NewMessage } from './types.js';
import { formatLocalTime } from './timezone.js';
/**
* Inbound message routing for v2.
*
* Channel adapter event → resolve messaging group → resolve agent group
* → resolve/create session → write messages_in → wake container
*/
import { getMessagingGroupByPlatform, createMessagingGroup, getMessagingGroupAgents } from './db/messaging-groups.js';
import { log } from './log.js';
import { resolveSession, writeSessionMessage } from './session-manager.js';
import { wakeContainer } from './container-runner.js';
import { getSession } from './db/sessions.js';
import type { MessagingGroupAgent } from './types.js';
export function escapeXml(s: string): string {
if (!s) return '';
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
function generateId(): string {
return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
}
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>`;
export interface InboundEvent {
channelType: string;
platformId: string;
threadId: string | null;
message: {
id: string;
kind: 'chat' | 'chat-sdk';
content: string; // JSON blob
timestamp: string;
};
}
/**
* Route an inbound message from a channel adapter to the correct session.
* Creates messaging group + session if they don't exist yet.
*/
export async function routeInbound(event: InboundEvent): Promise<void> {
// 1. Resolve messaging group
let mg = getMessagingGroupByPlatform(event.channelType, event.platformId);
if (!mg) {
// Auto-create messaging group (adapter already decided to forward this)
const mgId = `mg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
mg = {
id: mgId,
channel_type: event.channelType,
platform_id: event.platformId,
name: null,
is_group: 0,
admin_user_id: null,
created_at: new Date().toISOString(),
};
createMessagingGroup(mg);
log.info('Auto-created messaging group', {
id: mgId,
channelType: event.channelType,
platformId: event.platformId,
});
}
// 2. Resolve agent group via messaging_group_agents
const agents = getMessagingGroupAgents(mg.id);
if (agents.length === 0) {
log.warn('No agent groups configured for messaging group', {
messagingGroupId: mg.id,
platformId: event.platformId,
});
return;
}
// Pick the best matching agent (highest priority, trigger matching in future)
const match = pickAgent(agents, event);
if (!match) {
log.debug('No agent matched for message', { messagingGroupId: mg.id });
return;
}
// 3. Resolve or create session
const { session, created } = resolveSession(match.agent_group_id, mg.id, event.threadId, match.session_mode);
// 4. Write message to session DB
writeSessionMessage(session.agent_group_id, session.id, {
id: event.message.id || generateId(),
kind: event.message.kind,
timestamp: event.message.timestamp,
platformId: event.platformId,
channelType: event.channelType,
threadId: event.threadId,
content: event.message.content,
});
const header = `<context timezone="${escapeXml(timezone)}" />\n`;
log.info('Message routed', {
sessionId: session.id,
agentGroup: match.agent_group_id,
kind: event.message.kind,
created,
});
return `${header}<messages>\n${lines.join('\n')}\n</messages>`;
// 5. Wake container
const freshSession = getSession(session.id);
if (freshSession) {
await wakeContainer(freshSession);
}
}
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));
/**
* Pick the matching agent for an inbound event.
* Currently: highest priority agent. Future: trigger rule matching.
*/
function pickAgent(agents: MessagingGroupAgent[], _event: InboundEvent): MessagingGroupAgent | null {
// Agents are already ordered by priority DESC from the DB query
// TODO: apply trigger_rules matching (pattern, mentionOnly, etc.)
return agents[0] ?? null;
}