Files
nanoclaw/src/modules/permissions/db/pending-channel-approvals.ts
Gabi Simons db19837740 feat(permissions): richer channel-approval flow with agent selection and free-text naming
Replace the hardcoded Approve/Ignore card with a multi-step flow:
- Single agent: "Connect to [name]" / "Connect new agent" / "Reject"
- Multiple agents: "Choose existing agent" (follow-up list) / "Connect new agent" / "Reject"
- "Connect new agent" prompts for a free-text name via DM, creates immediately on reply
- Add setMessageInterceptor router hook for capturing free-text replies
- Add resolveChannelName optional method to ChannelAdapter interface

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 13:34:10 +00:00

63 lines
2.4 KiB
TypeScript

/**
* CRUD for pending_channel_approvals — the in-flight state for the
* unknown-channel registration flow. A row exists while an owner-approval
* card is outstanding; it's deleted on approve (after wiring is created)
* or deny (after denied_at is set on the messaging_group).
*
* PRIMARY KEY on messaging_group_id gives free in-flight dedup. A second
* mention/DM while a card is pending resolves via
* `hasInFlightChannelApproval` in the request flow and drops silently
* instead of spamming the owner.
*/
import { getDb } from '../../../db/connection.js';
export interface PendingChannelApproval {
messaging_group_id: string;
agent_group_id: string;
original_message: string;
approver_user_id: string;
created_at: string;
/** Card title shown at creation and re-used by getAskQuestionRender on click. */
title: string;
/** Normalized options (JSON-encoded NormalizedOption[]) — same shape persisted on pending_approvals. */
options_json: string;
}
export function createPendingChannelApproval(row: PendingChannelApproval): void {
getDb()
.prepare(
`INSERT INTO pending_channel_approvals (
messaging_group_id, agent_group_id, original_message,
approver_user_id, created_at, title, options_json
)
VALUES (
@messaging_group_id, @agent_group_id, @original_message,
@approver_user_id, @created_at, @title, @options_json
)`,
)
.run(row);
}
export function getPendingChannelApproval(messagingGroupId: string): PendingChannelApproval | undefined {
return getDb()
.prepare('SELECT * FROM pending_channel_approvals WHERE messaging_group_id = ?')
.get(messagingGroupId) as PendingChannelApproval | undefined;
}
export function hasInFlightChannelApproval(messagingGroupId: string): boolean {
const row = getDb()
.prepare('SELECT 1 AS x FROM pending_channel_approvals WHERE messaging_group_id = ?')
.get(messagingGroupId) as { x: number } | undefined;
return row !== undefined;
}
export function updatePendingChannelApprovalCard(messagingGroupId: string, title: string, optionsJson: string): void {
getDb()
.prepare('UPDATE pending_channel_approvals SET title = ?, options_json = ? WHERE messaging_group_id = ?')
.run(title, optionsJson, messagingGroupId);
}
export function deletePendingChannelApproval(messagingGroupId: string): void {
getDb().prepare('DELETE FROM pending_channel_approvals WHERE messaging_group_id = ?').run(messagingGroupId);
}