Files
nanoclaw/src/channels/adapter.ts
gavrielc cc784ff94b refactor(v2): remove trigger_credential_collection MCP tool
Drops the in-chat credential-collection flow introduced in e92b245. Agents
can no longer collect API keys via a secure modal — users must add secrets
through OneCLI directly. Keeps the OneCLI manual-approval handler and
threaded-routing work from the same commit intact.

Removed:
* container/agent-runner/src/mcp-tools/credentials.ts (MCP tool)
* src/credentials.ts (host-side modal/OneCLI pipeline)
* src/db/credentials.ts + migration 005 (pending_credentials table)
* src/onecli-secrets.ts (createSecret CLI facade, only caller was credentials.ts)
* findCredentialResponse from agent-runner DB layer
* PendingCredential types
* Four credential hooks from ChannelSetup (getCredentialForModal,
  onCredentialReject, onCredentialSubmit, onCredentialChannelUnsupported)
* Credential card/modal handling in chat-sdk-bridge (nccr/nccm prefixes,
  Modal/TextInput imports)
* credential_request text fallback in WhatsApp adapter
* request_credential system-action case in delivery.ts

Added:
* Migration 009 drops pending_credentials on existing installs.

Vercel skill now tells the agent to ask the user to register the token via
OneCLI instead of invoking the removed tool.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 21:41:41 +03:00

119 lines
4.3 KiB
TypeScript

/**
* v2 Channel Adapter interface.
*
* Channel adapters bridge NanoClaw with messaging platforms (Discord, Slack, etc.).
* Two patterns: native adapters (implement directly) or Chat SDK bridge (wrap a Chat SDK adapter).
*/
/** Configuration for a registered conversation (messaging group + agent wiring). */
export interface ConversationConfig {
platformId: string;
agentGroupId: string;
triggerPattern?: string; // regex string (for native channels)
requiresTrigger: boolean;
sessionMode: 'shared' | 'per-thread' | 'agent-shared';
}
/** Passed to the adapter at setup time. */
export interface ChannelSetup {
/** Known conversations from central DB. */
conversations: ConversationConfig[];
/** Called when an inbound message arrives from the platform. */
onInbound(platformId: string, threadId: string | null, message: InboundMessage): void | Promise<void>;
/** Called when the adapter discovers metadata about a conversation. */
onMetadata(platformId: string, name?: string, isGroup?: boolean): void;
/** Called when a user clicks a button/action in a card (e.g., ask_user_question response). */
onAction(questionId: string, selectedOption: string, userId: string): void;
}
/** Inbound message from adapter to host. */
export interface InboundMessage {
id: string;
kind: 'chat' | 'chat-sdk';
content: unknown; // JS object — host will JSON.stringify before writing to session DB
timestamp: string;
}
/** A file attachment to deliver alongside a message. */
export interface OutboundFile {
filename: string;
data: Buffer;
}
/** Outbound message from host to adapter. */
export interface OutboundMessage {
kind: string;
content: unknown; // parsed JSON from messages_out
files?: OutboundFile[]; // file attachments from the session outbox
}
/** Discovered conversation info (from syncConversations). */
export interface ConversationInfo {
platformId: string;
name: string;
isGroup: boolean;
}
/** The v2 channel adapter contract. */
export interface ChannelAdapter {
name: string;
channelType: string;
/**
* Whether this adapter models conversations as threads.
*
* true — adapter's platform uses threads as the primary conversation unit
* (Discord, Slack, Linear, GitHub). One thread = one session; the
* agent replies into the originating thread.
* false — adapter's platform treats the channel itself as the conversation
* (Telegram, WhatsApp, iMessage). Thread ids are stripped at the
* router; agent replies go to the channel.
*/
supportsThreads: boolean;
// Lifecycle
setup(config: ChannelSetup): Promise<void>;
teardown(): Promise<void>;
isConnected(): boolean;
// Outbound delivery — returns the platform message ID if available
deliver(platformId: string, threadId: string | null, message: OutboundMessage): Promise<string | undefined>;
// Optional
setTyping?(platformId: string, threadId: string | null): Promise<void>;
syncConversations?(): Promise<ConversationInfo[]>;
updateConversations?(conversations: ConversationConfig[]): void;
/**
* Open (or fetch) a DM with this user, returning the platform_id of the
* resulting DM channel. Called by the host on demand to initiate cold
* DMs — approvals, pairing handshakes, host-initiated notifications — to
* users who may never have messaged the bot themselves.
*
* Omit this method on channels where the user handle IS already the DM
* chat id (Telegram, WhatsApp, iMessage, email, Matrix). Callers will
* fall through to using the handle directly.
*
* For channels that distinguish user id from DM channel id (Discord,
* Slack, Teams, Webex, gChat): implement by delegating to Chat SDK's
* chat.openDM, which hits the platform's idempotent open-DM endpoint.
* Returning the same platform_id on repeated calls is expected.
*/
openDM?(userHandle: string): Promise<string>;
}
/** Factory function that creates a channel adapter (returns null if credentials missing). */
export type ChannelAdapterFactory = () => ChannelAdapter | Promise<ChannelAdapter> | null;
/** Registration entry for a channel adapter. */
export interface ChannelRegistration {
factory: ChannelAdapterFactory;
containerConfig?: {
mounts?: Array<{ hostPath: string; containerPath: string; readonly: boolean }>;
env?: Record<string, string>;
};
}