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

@@ -8,7 +8,7 @@ import fs from 'fs';
import type { ChannelAdapter, ChannelSetup, InboundMessage, OutboundMessage } from './adapter.js';
// Mock container runner
vi.mock('../container-runner-v2.js', () => ({
vi.mock('../container-runner.js', () => ({
wakeContainer: vi.fn().mockResolvedValue(undefined),
resetContainerIdleTimer: vi.fn(),
isContainerRunning: vi.fn().mockReturnValue(false),
@@ -160,7 +160,7 @@ describe('channel + router integration', () => {
});
it('should route inbound message from adapter to session DB', async () => {
const { routeInbound } = await import('../router-v2.js');
const { routeInbound } = await import('../router.js');
const { findSession } = await import('../db/sessions.js');
const { sessionDbPath } = await import('../session-manager.js');
@@ -209,7 +209,7 @@ describe('channel + router integration', () => {
onAction: () => {},
}));
// Set up delivery adapter bridge (same pattern as index-v2.ts)
// Set up delivery adapter bridge (same pattern as index.ts)
setDeliveryAdapter({
async deliver(channelType, platformId, threadId, kind, content) {
const adapter = getChannelAdapter(channelType);

View File

@@ -20,6 +20,6 @@ registerChannelAdapter('imessage', {
serverUrl: env.IMESSAGE_SERVER_URL,
apiKey: env.IMESSAGE_API_KEY,
});
return createChatSdkBridge({ adapter: imessageAdapter, concurrency: 'concurrent' });
return createChatSdkBridge({ adapter: imessageAdapter as never, concurrency: 'concurrent' });
},
});

View File

@@ -2,40 +2,40 @@
// Each import triggers the channel module's registerChannelAdapter() call.
// discord
// import './discord-v2.js';
// import './discord.js';
// slack
// import './slack-v2.js';
// import './slack.js';
// telegram
// import './telegram-v2.js';
// import './telegram.js';
// github
// import './github-v2.js';
// import './github.js';
// linear
// import './linear-v2.js';
// import './linear.js';
// google chat
// import './gchat-v2.js';
// import './gchat.js';
// microsoft teams
// import './teams-v2.js';
// import './teams.js';
// whatsapp cloud api
// import './whatsapp-cloud-v2.js';
// import './whatsapp-cloud.js';
// resend (email)
// import './resend-v2.js';
// import './resend.js';
// matrix
// import './matrix-v2.js';
// import './matrix.js';
// webex
// import './webex-v2.js';
// import './webex.js';
// imessage
// import './imessage-v2.js';
// import './imessage.js';
// gmail (native, no Chat SDK)

View File

@@ -1,38 +0,0 @@
import { describe, it, expect } from 'vitest';
import { registerChannel, getChannelFactory, getRegisteredChannelNames } from './registry.js';
// The registry is module-level state, so we need a fresh module per test.
// We use dynamic import with cache-busting to isolate tests.
// However, since vitest runs each file in its own context and we control
// registration order, we can test the public API directly.
describe('channel registry', () => {
// Note: registry is shared module state across tests in this file.
// Tests are ordered to account for cumulative registrations.
it('getChannelFactory returns undefined for unknown channel', () => {
expect(getChannelFactory('nonexistent')).toBeUndefined();
});
it('registerChannel and getChannelFactory round-trip', () => {
const factory = () => null;
registerChannel('test-channel', factory);
expect(getChannelFactory('test-channel')).toBe(factory);
});
it('getRegisteredChannelNames includes registered channels', () => {
registerChannel('another-channel', () => null);
const names = getRegisteredChannelNames();
expect(names).toContain('test-channel');
expect(names).toContain('another-channel');
});
it('later registration overwrites earlier one', () => {
const factory1 = () => null;
const factory2 = () => null;
registerChannel('overwrite-test', factory1);
registerChannel('overwrite-test', factory2);
expect(getChannelFactory('overwrite-test')).toBe(factory2);
});
});

View File

@@ -1,23 +0,0 @@
import { Channel, OnInboundMessage, OnChatMetadata, RegisteredGroup } from '../types.js';
export interface ChannelOpts {
onMessage: OnInboundMessage;
onChatMetadata: OnChatMetadata;
registeredGroups: () => Record<string, RegisteredGroup>;
}
export type ChannelFactory = (opts: ChannelOpts) => Channel | null;
const registry = new Map<string, ChannelFactory>();
export function registerChannel(name: string, factory: ChannelFactory): void {
registry.set(name, factory);
}
export function getChannelFactory(name: string): ChannelFactory | undefined {
return registry.get(name);
}
export function getRegisteredChannelNames(): string[] {
return [...registry.keys()];
}

View File

@@ -11,7 +11,12 @@ import { registerChannelAdapter } from './channel-registry.js';
registerChannelAdapter('whatsapp-cloud', {
factory: () => {
const env = readEnvFile(['WHATSAPP_ACCESS_TOKEN', 'WHATSAPP_PHONE_NUMBER_ID', 'WHATSAPP_APP_SECRET', 'WHATSAPP_VERIFY_TOKEN']);
const env = readEnvFile([
'WHATSAPP_ACCESS_TOKEN',
'WHATSAPP_PHONE_NUMBER_ID',
'WHATSAPP_APP_SECRET',
'WHATSAPP_VERIFY_TOKEN',
]);
if (!env.WHATSAPP_ACCESS_TOKEN) return null;
const whatsappAdapter = createWhatsAppAdapter({
accessToken: env.WHATSAPP_ACCESS_TOKEN,