Revert "fix(permissions): welcome new approved channels via /welcome, route to them"

This reverts commit 9776dd4f32.
This commit is contained in:
gavrielc
2026-04-21 15:20:06 +03:00
parent 9776dd4f32
commit 01ffce6f74
3 changed files with 5 additions and 181 deletions

View File

@@ -247,87 +247,6 @@ describe('unknown-channel registration flow', () => {
expect(wakeContainer).toHaveBeenCalled();
});
it('approve → seeds a /welcome onboarding task into the session', async () => {
const { routeInbound } = await import('../../router.js');
const { getResponseHandlers } = await import('../../response-registry.js');
// `/start` is filtered by the agent-runner (Claude Code slash command),
// so without the seeded onboarding task a Telegram user's first DM would
// produce zero response. The seed ensures the agent runs /welcome regardless.
const startDm = {
channelType: 'telegram',
platformId: 'dm-new-friend',
threadId: null,
message: {
id: 'msg-start',
kind: 'chat' as const,
content: JSON.stringify({ senderId: 'friend', senderName: 'Friend', text: '/start' }),
timestamp: now(),
isMention: true,
},
};
await routeInbound(startDm);
await new Promise((r) => setTimeout(r, 10));
const { getDb } = await import('../../db/connection.js');
const pending = getDb()
.prepare('SELECT messaging_group_id, agent_group_id FROM pending_channel_approvals')
.get() as { messaging_group_id: string; agent_group_id: string };
for (const handler of getResponseHandlers()) {
const claimed = await handler({
questionId: pending.messaging_group_id,
value: 'approve',
userId: 'owner',
channelType: 'telegram',
platformId: 'dm-owner',
threadId: null,
});
if (claimed) break;
}
// Look up the session that got created, then open its inbound.db and
// confirm an onboarding task with a /welcome prompt landed before the
// replayed chat message.
const session = getDb()
.prepare('SELECT id FROM sessions WHERE agent_group_id = ? AND messaging_group_id = ?')
.get(pending.agent_group_id, pending.messaging_group_id) as { id: string } | undefined;
expect(session).toBeDefined();
const Database = (await import('better-sqlite3')).default;
const path = await import('path');
const inboundPath = path.join(TEST_DIR, 'v2-sessions', pending.agent_group_id, session!.id, 'inbound.db');
const inbound = new Database(inboundPath, { readonly: true });
const rows = inbound
.prepare('SELECT kind, content, seq FROM messages_in ORDER BY seq')
.all() as { kind: string; content: string; seq: number }[];
inbound.close();
const taskRow = rows.find((r) => r.kind === 'task');
expect(taskRow).toBeDefined();
const prompt: string = JSON.parse(taskRow!.content).prompt;
expect(prompt).toMatch(/\/welcome/);
// Prompt must name the new user — otherwise with multiple destinations
// configured the model may greet the owner instead of the new sender
// (see bug where "Hey Daniel!" landed in the owner's DM).
expect(prompt).toContain('Friend');
expect(prompt).toContain('dm-new-friend');
// Prompt must pin the exact destination by its agent_destinations
// local_name. That name is auto-created by createMessagingGroupAgent
// above; look it up and assert it appears in the prompt verbatim.
const destRow = getDb()
.prepare('SELECT local_name FROM agent_destinations WHERE agent_group_id = ? AND target_id = ?')
.get(pending.agent_group_id, pending.messaging_group_id) as { local_name: string };
expect(destRow).toBeDefined();
expect(prompt).toContain(`send_message(to: '${destRow.local_name}'`);
// Order: task seeded before the replayed /start chat message.
const chatRow = rows.find((r) => r.kind === 'chat');
expect(chatRow).toBeDefined();
expect(taskRow!.seq).toBeLessThan(chatRow!.seq);
});
it('approve on a DM wires with pattern="." defaults', async () => {
const { routeInbound } = await import('../../router.js');
const { getResponseHandlers } = await import('../../response-registry.js');

View File

@@ -28,7 +28,6 @@ import {
import type { InboundEvent } from '../../channels/adapter.js';
import { registerResponseHandler, type ResponsePayload } from '../../response-registry.js';
import { log } from '../../log.js';
import { resolveSession, writeSessionMessage } from '../../session-manager.js';
import type { MessagingGroup, MessagingGroupAgent } from '../../types.js';
import { canAccessAgentGroup } from './access.js';
import { requestChannelApproval } from './channel-approval.js';
@@ -380,75 +379,6 @@ async function handleChannelApprovalResponse(payload: ResponsePayload): Promise<
// path normally.
deletePendingChannelApproval(row.messaging_group_id);
// Seed a /welcome onboarding task into the session *before* the replayed
// message so the agent greets the new user on first contact. Mirrors the
// owner-setup path (setup/register.ts). Without this, a Telegram user's
// default `/start` greeting gets silently dropped by the agent-runner's
// command filter and the first interaction produces nothing.
//
// The prompt pins the exact destination by name: createMessagingGroupAgent
// above auto-created an `agent_destinations` row pointing at this messaging
// group, and we look it up here. Without that, the agent — which already
// knows about the owner's DM and earlier friends' DMs as named destinations
// — tends to greet the owner (CLAUDE.md anchors on them). Passing the
// specific destination name removes the ambiguity entirely.
try {
const { session } = resolveSession(row.agent_group_id, row.messaging_group_id, event.threadId, 'shared');
const parsed = safeParseContent(event.message.content);
const author =
parsed && typeof parsed === 'object' && 'author' in parsed && typeof (parsed as { author?: unknown }).author === 'object'
? ((parsed as { author: Record<string, unknown> }).author)
: undefined;
const senderName =
(typeof (parsed as { senderName?: unknown }).senderName === 'string' ? (parsed as { senderName: string }).senderName : undefined) ??
(typeof (parsed as { sender?: unknown }).sender === 'string' ? (parsed as { sender: string }).sender : undefined) ??
(typeof author?.fullName === 'string' ? (author.fullName as string) : undefined) ??
(typeof author?.userName === 'string' ? (author.userName as string) : undefined) ??
null;
const senderLabel = senderName ? `${senderName} (${event.platformId})` : event.platformId;
// Pin the destination. Guarded behind hasTable in case the agent-to-agent
// module isn't installed — without it there are no named destinations at
// all, so the agent's send_message call falls through to session default
// routing (which is this new user). Either way, unambiguous.
let destinationClause: string;
const { hasTable, getDb } = await import('../../db/connection.js');
if (hasTable(getDb(), 'agent_destinations')) {
const { getDestinationByTarget } = await import('../agent-to-agent/db/agent-destinations.js');
const dest = getDestinationByTarget(row.agent_group_id, 'channel', row.messaging_group_id);
destinationClause = dest
? `Send your welcome with send_message(to: '${dest.local_name}', text: ...) — that destination resolves to their DM.`
: `Reply using send_message with no \`to\` — the session's default routing points at their DM.`;
} else {
destinationClause = `Reply using send_message — it will land in their DM.`;
}
writeSessionMessage(row.agent_group_id, session.id, {
id: `onboard-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
kind: 'task',
timestamp: new Date().toISOString(),
platformId: event.platformId,
channelType: event.channelType,
content: JSON.stringify({
prompt:
`A new ${event.channelType} user — ${senderLabel} — just started a conversation. ` +
`Run /welcome to introduce yourself to them. ${destinationClause}`,
}),
});
log.info('Onboarding message seeded after channel approval', {
sessionId: session.id,
messagingGroupId: row.messaging_group_id,
agentGroupId: row.agent_group_id,
senderName,
});
} catch (err) {
// Don't block the replay if onboarding write fails.
log.error('Failed to seed onboarding message after channel approval', {
messagingGroupId: row.messaging_group_id,
err,
});
}
try {
await routeInbound(event);
} catch (err) {