feat(v2): track unregistered senders + setup improvements

- Add unregistered_senders table to capture dropped message origins
  (one row per sender, upserted with message_count and last_seen)
- Add inbound DM logging to chat-sdk-bridge for debugging
- Add vercel CLI to base container image
- Install vercel-cli and frontend-engineer container skills
- Default requiresTrigger to false in register step

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-16 12:58:40 +03:00
parent 57c9bfc670
commit 39d2af9981
9 changed files with 412 additions and 6 deletions

View File

@@ -0,0 +1,44 @@
import { getDb } from './connection.js';
export interface UnregisteredSender {
channel_type: string;
platform_id: string;
user_id: string | null;
sender_name: string | null;
reason: string;
messaging_group_id: string | null;
agent_group_id: string | null;
message_count: number;
first_seen: string;
last_seen: string;
}
export function recordDroppedMessage(msg: {
channel_type: string;
platform_id: string;
user_id: string | null;
sender_name: string | null;
reason: string;
messaging_group_id: string | null;
agent_group_id: string | null;
}): void {
const now = new Date().toISOString();
getDb()
.prepare(
`INSERT INTO unregistered_senders (channel_type, platform_id, user_id, sender_name, reason, messaging_group_id, agent_group_id, message_count, first_seen, last_seen)
VALUES (@channel_type, @platform_id, @user_id, @sender_name, @reason, @messaging_group_id, @agent_group_id, 1, @now, @now)
ON CONFLICT (channel_type, platform_id) DO UPDATE SET
user_id = COALESCE(excluded.user_id, unregistered_senders.user_id),
sender_name = COALESCE(excluded.sender_name, unregistered_senders.sender_name),
reason = excluded.reason,
message_count = unregistered_senders.message_count + 1,
last_seen = excluded.last_seen`,
)
.run({ ...msg, now });
}
export function getUnregisteredSenders(limit = 50): UnregisteredSender[] {
return getDb()
.prepare('SELECT * FROM unregistered_senders ORDER BY last_seen DESC LIMIT ?')
.all(limit) as UnregisteredSender[];
}

View File

@@ -0,0 +1,27 @@
import type Database from 'better-sqlite3';
import type { Migration } from './index.js';
export const migration008: Migration = {
version: 8,
name: 'dropped-messages',
up: (db: Database.Database) => {
db.exec(`
CREATE TABLE IF NOT EXISTS unregistered_senders (
channel_type TEXT NOT NULL,
platform_id TEXT NOT NULL,
user_id TEXT,
sender_name TEXT,
reason TEXT NOT NULL,
messaging_group_id TEXT,
agent_group_id TEXT,
message_count INTEGER NOT NULL DEFAULT 1,
first_seen TEXT NOT NULL,
last_seen TEXT NOT NULL,
PRIMARY KEY (channel_type, platform_id)
);
CREATE INDEX IF NOT EXISTS idx_unregistered_senders_last_seen
ON unregistered_senders(last_seen);
`);
},
};

View File

@@ -7,6 +7,7 @@ import { migration003 } from './003-pending-approvals.js';
import { migration004 } from './004-agent-destinations.js';
import { migration005 } from './005-pending-credentials.js';
import { migration007 } from './007-pending-approvals-title-options.js';
import { migration008 } from './008-dropped-messages.js';
export interface Migration {
version: number;
@@ -14,7 +15,7 @@ export interface Migration {
up: (db: Database.Database) => void;
}
const migrations: Migration[] = [migration001, migration002, migration003, migration004, migration005, migration007];
const migrations: Migration[] = [migration001, migration002, migration003, migration004, migration005, migration007, migration008];
export function runMigrations(db: Database.Database): void {
db.exec(`