/** * Ops Tools — 4 operational tools * * set_strategy, get_status, set_purpose, get_purpose * * Contract ref: §0.2 refactoring-plan.md */ import { registry } from '../runtime/registry.js'; import { toolResult, type ToolDescriptor, 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 createSetStrategyTool(): ToolDescriptor { return { name: 'clawlink_set_strategy', label: 'Set Message Strategy', description: 'Configure how you receive and respond to channel messages. Changes take effect immediately.', parameters: { type: 'object', properties: { batchWindowMs: { type: 'integer', description: 'How long to collect messages before processing (ms, default 20000)' }, maxBatchSize: { type: 'integer', description: 'Max messages to buffer before forced flush (default 20)' }, }, required: [], }, async execute(_id, params) { const rt = getRuntime(); if (!rt) return fail('Not connected'); try { const newConfig: { batchWindowMs?: number; maxBatchSize?: number } = {}; if (params?.batchWindowMs != null) newConfig.batchWindowMs = Number(params.batchWindowMs); if (params?.maxBatchSize != null) newConfig.maxBatchSize = Number(params.maxBatchSize); const current = rt.updateStrategy(newConfig); return toolResult(JSON.stringify({ success: true, batchWindowMs: current.batchWindowMs, maxBatchSize: current.maxBatchSize, })); } catch (err) { return fail((err as Error).message); } }, }; } /** * get_status — frozen return shape per §0.2: * * { * success, connected, agentId, * schedulerQueueSize, joinedChannels, * channels: [{ id, name, lastVisit, lastReply, lastMsgSeq }] * } */ export function createGetStatusTool(): ToolDescriptor { return { name: 'clawlink_get_status', label: 'Get ClawLink Status', description: 'Get current status of the ClawLink connection and channel state', parameters: { type: 'object', properties: {}, required: [] }, async execute() { const rt = getRuntime(); if (!rt) { return fail('ClawLink is not running'); } try { const joined = rt.store?.getJoinedChannels() || []; return toolResult(JSON.stringify({ success: true, connected: rt.client?.isReady ?? false, agentId: rt.client?.agentId ?? '', schedulerQueueSize: rt.schedulerQueueSize, joinedChannels: joined.length, channels: joined.map((c: { channel_id: string; channel_name: string; last_visit: number; last_reply: number; last_msg_seq: number }) => ({ id: c.channel_id, name: c.channel_name, lastVisit: c.last_visit ? new Date(c.last_visit).toISOString() : 'never', lastReply: c.last_reply ? new Date(c.last_reply).toISOString() : 'never', lastMsgSeq: c.last_msg_seq, })), })); } catch (err) { return fail((err as Error).message); } }, }; } export function createSetPurposeTool(): ToolDescriptor { return { name: 'clawlink_set_purpose', label: 'Set Channel Purpose', description: 'Set or update your purpose/goal for a channel', parameters: { type: 'object', properties: { channelId: { type: 'string', description: 'Channel ID' }, purpose: { type: 'string', description: 'Your purpose/goal for this channel' }, }, required: ['channelId', 'purpose'], }, async execute(_id, params) { const rt = getRuntime(); if (!rt?.store) return fail('Not connected'); try { rt.store.setPurpose(String(params.channelId), String(params.purpose)); return toolResult(JSON.stringify({ success: true, channelId: params.channelId, purpose: params.purpose, })); } catch (err) { return fail((err as Error).message); } }, }; } export function createGetPurposeTool(): ToolDescriptor { return { name: 'clawlink_get_purpose', label: 'Get Channel Purpose', description: 'Get your current purpose/goal for a channel', parameters: { type: 'object', properties: { channelId: { type: 'string', description: 'Channel ID' }, }, required: ['channelId'], }, async execute(_id, params) { const rt = getRuntime(); if (!rt?.store) return fail('Not connected'); try { const purpose = rt.store.getPurpose(String(params.channelId)); return toolResult(JSON.stringify({ success: true, channelId: params.channelId, purpose: purpose || '(no purpose set)', })); } catch (err) { return fail((err as Error).message); } }, }; }