/** * Channel Tools — 8 channel management tools * * list_channels, search_channels, join_channel, leave_channel, * create_channel, get_members, set_channel_skill, get_channel_skill * * Contract ref: §0.2 refactoring-plan.md */ import { registry } from '../runtime/registry.js'; import { toolResult, type ToolDescriptor, type ToolParams, type ToolResult } from './types.js'; function fail(error: string): ToolResult { return toolResult(JSON.stringify({ success: false, error })); } function getRuntime() { const rt = registry.getDefault(); if (!rt || !rt.isRunning) return null; return rt; } export function createListChannelsTool(): ToolDescriptor { return { name: 'clawlink_list_channels', label: 'List ClawLink Channels', description: 'List all available channels on ClawLink agent network', parameters: { type: 'object', properties: {}, required: [] }, async execute() { const rt = getRuntime(); if (!rt) return fail('Not connected'); try { const channels = await rt.listChannels(); return toolResult(JSON.stringify({ success: true, channels })); } catch (err) { return fail((err as Error).message); } }, }; } export function createSearchChannelsTool(): ToolDescriptor { return { name: 'clawlink_search_channels', label: 'Search ClawLink Channels', description: 'Search channels by keyword', parameters: { type: 'object', properties: { keyword: { type: 'string', description: 'Search keyword' } }, required: ['keyword'], }, async execute(_id: unknown, params: ToolParams) { const rt = getRuntime(); if (!rt) return fail('Not connected'); try { const channels = await rt.searchChannels(String(params.keyword || '')); return toolResult(JSON.stringify({ success: true, channels })); } catch (err) { return fail((err as Error).message); } }, }; } /** * join_channel — supports optional `purpose?` per §0.2. * If purpose is provided, it is stored via setPurpose(). */ export function createJoinChannelTool(): ToolDescriptor { return { name: 'clawlink_join_channel', label: 'Join ClawLink Channel', description: 'Join a channel by ID (e.g. ch-001). Optionally set a purpose for this channel.', parameters: { type: 'object', properties: { channelId: { type: 'string', description: 'Channel ID to join' }, purpose: { type: 'string', description: 'Optional: your purpose/goal for this channel' }, }, required: ['channelId'], }, async execute(_id: unknown, params: ToolParams) { const rt = getRuntime(); if (!rt) return fail('Not connected'); try { const channelId = String(params.channelId); const result = await rt.joinChannel(channelId); if (result.success && rt.store) { rt.store.markJoined(channelId, channelId); if (params.purpose) { rt.store.setPurpose(channelId, String(params.purpose)); } } return toolResult(JSON.stringify({ success: result.success, channelId, purpose: params.purpose ? String(params.purpose) : undefined, })); } catch (err) { return fail((err as Error).message); } }, }; } export function createLeaveChannelTool(): ToolDescriptor { return { name: 'clawlink_leave_channel', label: 'Leave ClawLink Channel', description: 'Leave a channel by ID', parameters: { type: 'object', properties: { channelId: { type: 'string', description: 'Channel ID to leave' } }, required: ['channelId'], }, async execute(_id: unknown, params: ToolParams) { const rt = getRuntime(); if (!rt) return fail('Not connected'); try { const channelId = String(params.channelId); const result = await rt.leaveChannel(channelId); if (result.success && rt.store) { rt.store.markLeft(channelId); } return toolResult(JSON.stringify(result)); } catch (err) { return fail((err as Error).message); } }, }; } /** * create_channel — supports optional `channelId?`, `skill?`, `desc?` per §0.2. * If channelId is not given, auto-generates one. */ export function createCreateChannelTool(): ToolDescriptor { return { name: 'clawlink_create_channel', label: 'Create ClawLink Channel', description: 'Create a new channel. You become the owner and are automatically joined.', parameters: { type: 'object', properties: { name: { type: 'string', description: 'Channel display name (e.g. "AI Research Discussion")' }, desc: { type: 'string', description: 'Short description of the channel topic' }, skill: { type: 'string', description: 'Optional initial channel skill/rules' }, }, required: ['name'], }, async execute(_id: unknown, params: ToolParams) { const rt = getRuntime(); if (!rt) return fail('Not connected'); try { // Community groups: SDK auto-generates @TGS#_ prefixed groupID const result = await rt.createChannel( String(params.name), String(params.desc || ''), params.skill ? String(params.skill) : undefined, ); return toolResult(JSON.stringify(result)); } catch (err) { return fail((err as Error).message); } }, }; } /** * get_members — returns avatar? field per §0.2. */ export function createGetMembersTool(): ToolDescriptor { return { name: 'clawlink_get_members', label: 'Get Channel Members', description: 'Get the member list of a channel', parameters: { type: 'object', properties: { channelId: { type: 'string', description: 'Channel ID' } }, required: ['channelId'], }, async execute(_id: unknown, params: ToolParams) { const rt = getRuntime(); if (!rt) return fail('Not connected'); try { const members = await rt.getMembers(String(params.channelId)); // Preserve all fields including avatar? per contract return toolResult(JSON.stringify({ success: true, members })); } catch (err) { return fail((err as Error).message); } }, }; } export function createSetChannelSkillTool(): ToolDescriptor { return { name: 'clawlink_set_channel_skill', label: 'Set Channel Skill', description: 'Set the skill/behavior rules for a channel', parameters: { type: 'object', properties: { channelId: { type: 'string', description: 'Channel ID' }, skill: { type: 'string', description: 'Channel rules that control agent behavior' }, }, required: ['channelId', 'skill'], }, async execute(_id: unknown, params: ToolParams) { const rt = getRuntime(); if (!rt) return fail('Not connected'); try { await rt.setChannelSkill(String(params.channelId), String(params.skill)); return toolResult(JSON.stringify({ success: true, message: `Skill set for ${params.channelId}` })); } catch (err) { return fail((err as Error).message); } }, }; } export function createGetChannelSkillTool(): ToolDescriptor { return { name: 'clawlink_get_channel_skill', label: 'Get Channel Skill', description: 'Get the current skill/behavior rules for a channel', parameters: { type: 'object', properties: { channelId: { type: 'string', description: 'Channel ID' }, }, required: ['channelId'], }, async execute(_id: unknown, params: ToolParams) { const rt = getRuntime(); if (!rt) return fail('Not connected'); try { const skill = await rt.getChannelSkill(String(params.channelId)); return toolResult(JSON.stringify({ success: true, channelId: params.channelId, skill: skill || '(no skill set)', })); } catch (err) { return fail((err as Error).message); } }, }; }