@yeaft/webchat-agent 0.1.409 → 0.1.411

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,133 @@
1
+ /**
2
+ * mcp-tools.js — MCP (Model Context Protocol) tool bridge
3
+ *
4
+ * Provides two tools for interacting with MCP servers:
5
+ * - mcp_list_tools: Discover available MCP tools from connected servers
6
+ * - mcp_call_tool: Invoke an MCP tool by name with arguments
7
+ *
8
+ * These are exported as an array (unlike other tool files that export a single tool).
9
+ *
10
+ * Reference: yeaft-unify-design.md §8
11
+ */
12
+
13
+ import { defineTool } from './types.js';
14
+
15
+ export const mcpListTools = defineTool({
16
+ name: 'mcp_list_tools',
17
+ description: `List all tools available from connected MCP (Model Context Protocol) servers.
18
+
19
+ Usage guidelines:
20
+ - Use to discover what MCP tools are available in the current session
21
+ - Returns tool names, descriptions, and parameter schemas from all connected MCP servers
22
+ - Each tool is prefixed with the server name (e.g. "github__list_prs", "slack__send_message")
23
+ - Use this before mcp_call_tool to understand available capabilities
24
+ - MCP servers are configured by the user — if none are connected, returns an empty list`,
25
+ parameters: {
26
+ type: 'object',
27
+ properties: {
28
+ server: {
29
+ type: 'string',
30
+ description: 'Filter tools from a specific MCP server (optional)',
31
+ },
32
+ },
33
+ },
34
+ modes: ['chat', 'work'],
35
+ isConcurrencySafe: () => true,
36
+ isReadOnly: () => true,
37
+ async execute(input, ctx) {
38
+ const mcpManager = ctx?.mcpManager;
39
+
40
+ if (!mcpManager || !mcpManager.hasServers) {
41
+ return JSON.stringify({
42
+ tools: [],
43
+ message: 'No MCP servers are connected. Configure MCP servers in ~/.yeaft/config.md',
44
+ });
45
+ }
46
+
47
+ const tools = mcpManager.listTools(input.server || undefined);
48
+
49
+ return JSON.stringify({
50
+ tools: tools.map(t => ({
51
+ name: t.name,
52
+ server: t.server,
53
+ description: t.description,
54
+ inputSchema: t.inputSchema,
55
+ })),
56
+ totalCount: tools.length,
57
+ servers: mcpManager.status(),
58
+ }, null, 2);
59
+ },
60
+ });
61
+
62
+ export const mcpCallTool = defineTool({
63
+ name: 'mcp_call_tool',
64
+ description: `Call a tool on a connected MCP (Model Context Protocol) server.
65
+
66
+ Usage guidelines:
67
+ - Use after discovering tools via mcp_list_tools
68
+ - Provide the full tool name including server prefix (e.g. "github__create_issue")
69
+ - Arguments must match the tool's parameter schema exactly
70
+ - The tool executes on the MCP server and returns the result
71
+ - Timeouts depend on the MCP server — use timeout_ms if the operation is slow
72
+ - Errors from the MCP server are returned as structured error objects`,
73
+ parameters: {
74
+ type: 'object',
75
+ properties: {
76
+ tool_name: {
77
+ type: 'string',
78
+ description: 'Full tool name including server prefix (e.g. "github__list_prs")',
79
+ },
80
+ arguments: {
81
+ type: 'object',
82
+ description: 'Arguments to pass to the MCP tool (must match its schema)',
83
+ },
84
+ timeout_ms: {
85
+ type: 'number',
86
+ description: 'Timeout in milliseconds (default: 30000)',
87
+ },
88
+ },
89
+ required: ['tool_name'],
90
+ },
91
+ modes: ['chat', 'work'],
92
+ async execute(input, ctx) {
93
+ const mcpManager = ctx?.mcpManager;
94
+
95
+ if (!mcpManager || !mcpManager.hasServers) {
96
+ return JSON.stringify({
97
+ error: 'No MCP servers are connected. Configure MCP servers in ~/.yeaft/config.md',
98
+ });
99
+ }
100
+
101
+ const { tool_name, arguments: args = {}, timeout_ms } = input;
102
+
103
+ if (!tool_name) {
104
+ return JSON.stringify({ error: 'tool_name is required' });
105
+ }
106
+
107
+ try {
108
+ const result = await mcpManager.callTool(tool_name, args, timeout_ms || 30000);
109
+
110
+ // Format MCP result
111
+ if (result?.content) {
112
+ // MCP standard response format
113
+ const textParts = result.content
114
+ .filter(c => c.type === 'text')
115
+ .map(c => c.text);
116
+ if (textParts.length > 0) {
117
+ return textParts.join('\n');
118
+ }
119
+ }
120
+
121
+ return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
122
+
123
+ } catch (err) {
124
+ return JSON.stringify({
125
+ error: err.message,
126
+ tool: tool_name,
127
+ });
128
+ }
129
+ },
130
+ });
131
+
132
+ // Default export as array for compatibility with bulk registration
133
+ export default [mcpListTools, mcpCallTool];
@@ -0,0 +1,146 @@
1
+ /**
2
+ * registry.js — Tool registration center for Yeaft Unify
3
+ *
4
+ * Manages tool registration, mode-based filtering, and execution dispatch.
5
+ * The engine uses this to get tool definitions for the LLM and to execute tool calls.
6
+ */
7
+
8
+ /**
9
+ * Mode normalization map.
10
+ * 'coordinator' and 'worker' inherit 'work' tools.
11
+ * 'dream' has no tools by design (returns empty).
12
+ */
13
+ const MODE_ALIASES = {
14
+ coordinator: 'work',
15
+ worker: 'work',
16
+ };
17
+
18
+ export class ToolRegistry {
19
+ /** @type {Map<string, import('./types.js').ToolDef>} */
20
+ #tools = new Map();
21
+
22
+ /**
23
+ * Register a single tool.
24
+ * @param {import('./types.js').ToolDef} tool
25
+ * @returns {ToolRegistry}
26
+ */
27
+ register(tool) {
28
+ if (!tool || !tool.name) throw new Error('Tool must have a name');
29
+ this.#tools.set(tool.name, tool);
30
+ return this;
31
+ }
32
+
33
+ /**
34
+ * Register multiple tools.
35
+ * @param {import('./types.js').ToolDef[]} tools
36
+ * @returns {ToolRegistry}
37
+ */
38
+ registerAll(tools) {
39
+ for (const tool of tools) this.register(tool);
40
+ return this;
41
+ }
42
+
43
+ /**
44
+ * Unregister a tool by name.
45
+ * @param {string} name
46
+ * @returns {ToolRegistry}
47
+ */
48
+ unregister(name) {
49
+ this.#tools.delete(name);
50
+ return this;
51
+ }
52
+
53
+ /**
54
+ * Get a tool by name.
55
+ * @param {string} name
56
+ * @returns {import('./types.js').ToolDef | null}
57
+ */
58
+ get(name) {
59
+ return this.#tools.get(name) || null;
60
+ }
61
+
62
+ /**
63
+ * Check if a tool is registered.
64
+ * @param {string} name
65
+ * @returns {boolean}
66
+ */
67
+ has(name) {
68
+ return this.#tools.has(name);
69
+ }
70
+
71
+ /**
72
+ * Resolve a mode to its effective tool mode.
73
+ * @param {string} mode
74
+ * @returns {string}
75
+ */
76
+ static resolveMode(mode) {
77
+ return MODE_ALIASES[mode] || mode;
78
+ }
79
+
80
+ /**
81
+ * Get all tools available in a given mode.
82
+ * @param {string} mode
83
+ * @returns {import('./types.js').ToolDef[]}
84
+ */
85
+ getToolsForMode(mode) {
86
+ const effectiveMode = ToolRegistry.resolveMode(mode);
87
+ const result = [];
88
+ for (const [, tool] of this.#tools) {
89
+ if (tool.modes.includes(effectiveMode)) result.push(tool);
90
+ }
91
+ return result;
92
+ }
93
+
94
+ /**
95
+ * Get tool definitions for the LLM adapter.
96
+ * @param {string} mode
97
+ * @returns {{ name: string, description: string, parameters: object }[]}
98
+ */
99
+ getToolDefs(mode) {
100
+ return this.getToolsForMode(mode).map(t => ({
101
+ name: t.name,
102
+ description: t.description,
103
+ parameters: t.parameters,
104
+ }));
105
+ }
106
+
107
+ /**
108
+ * Get tool names available in a given mode.
109
+ * @param {string} mode
110
+ * @returns {string[]}
111
+ */
112
+ getToolNames(mode) {
113
+ return this.getToolsForMode(mode).map(t => t.name);
114
+ }
115
+
116
+ /**
117
+ * Execute a tool by name.
118
+ * @param {string} name
119
+ * @param {object} input
120
+ * @param {import('./types.js').ToolContext} [ctx={}]
121
+ * @returns {Promise<string>}
122
+ */
123
+ async execute(name, input, ctx = {}) {
124
+ const tool = this.#tools.get(name);
125
+ if (!tool) throw new Error(`Unknown tool: ${name}`);
126
+ return tool.execute(input, ctx);
127
+ }
128
+
129
+ /** Number of registered tools. */
130
+ get size() {
131
+ return this.#tools.size;
132
+ }
133
+
134
+ /** All registered tool names. */
135
+ get names() {
136
+ return Array.from(this.#tools.keys());
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Create an empty registry.
142
+ * @returns {ToolRegistry}
143
+ */
144
+ export function createEmptyRegistry() {
145
+ return new ToolRegistry();
146
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * skill.js — Skill invocation tool
3
+ *
4
+ * Allows the LLM to load and activate skills from the skill library.
5
+ * Skills are specialized behaviors defined in ~/.yeaft/skills/*.md.
6
+ *
7
+ * Reference: yeaft-unify-design.md §8
8
+ */
9
+
10
+ import { defineTool } from './types.js';
11
+
12
+ export default defineTool({
13
+ name: 'Skill',
14
+ description: `Load and activate a skill from the Yeaft skill library.
15
+
16
+ Skills are specialized behaviors or workflows defined in ~/.yeaft/skills/.
17
+ Use this tool to:
18
+ - List available skills
19
+ - Load a specific skill's instructions
20
+ - Find relevant skills for the current context
21
+
22
+ Skills provide domain-specific guidance and workflows that enhance your capabilities.`,
23
+ parameters: {
24
+ type: 'object',
25
+ properties: {
26
+ action: {
27
+ type: 'string',
28
+ enum: ['list', 'load', 'search'],
29
+ description: '"list" lists all skills, "load" loads a specific skill, "search" finds relevant skills',
30
+ },
31
+ name: {
32
+ type: 'string',
33
+ description: 'Skill name (for "load" action)',
34
+ },
35
+ query: {
36
+ type: 'string',
37
+ description: 'Search query (for "search" action)',
38
+ },
39
+ },
40
+ required: ['action'],
41
+ },
42
+ modes: ['chat', 'work'],
43
+ isConcurrencySafe: () => true,
44
+ isReadOnly: () => true,
45
+ async execute(input, ctx) {
46
+ const skillManager = ctx?.skillManager;
47
+
48
+ if (!skillManager) {
49
+ return JSON.stringify({
50
+ error: 'Skill system not initialized. Skills directory: ~/.yeaft/skills/',
51
+ });
52
+ }
53
+
54
+ switch (input.action) {
55
+ case 'list': {
56
+ const skills = skillManager.list();
57
+ if (skills.length === 0) {
58
+ return JSON.stringify({
59
+ skills: [],
60
+ message: 'No skills found. Add .md files to ~/.yeaft/skills/ to create skills.',
61
+ });
62
+ }
63
+ return JSON.stringify({
64
+ skills: skills.map(s => ({
65
+ name: s.name,
66
+ description: s.description || '',
67
+ trigger: s.trigger || '',
68
+ mode: s.mode || 'both',
69
+ })),
70
+ totalCount: skills.length,
71
+ }, null, 2);
72
+ }
73
+
74
+ case 'load': {
75
+ if (!input.name) {
76
+ return JSON.stringify({ error: 'Skill name is required for "load" action' });
77
+ }
78
+ const content = skillManager.getPromptContent(input.name);
79
+ if (!content) {
80
+ return JSON.stringify({
81
+ error: `Skill "${input.name}" not found`,
82
+ available: skillManager.list().map(s => s.name),
83
+ });
84
+ }
85
+ return content;
86
+ }
87
+
88
+ case 'search': {
89
+ if (!input.query) {
90
+ return JSON.stringify({ error: 'Query is required for "search" action' });
91
+ }
92
+ const results = skillManager.findRelevant(input.query);
93
+ return JSON.stringify({
94
+ results: results.map(s => ({
95
+ name: s.name,
96
+ description: s.description || '',
97
+ trigger: s.trigger || '',
98
+ })),
99
+ totalResults: results.length,
100
+ }, null, 2);
101
+ }
102
+
103
+ default:
104
+ return JSON.stringify({ error: `Unknown action: ${input.action}. Use "list", "load", or "search".` });
105
+ }
106
+ },
107
+ });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * types.js — Tool definition interface for Yeaft Unify
3
+ *
4
+ * All tools use the defineTool() function to create tool definitions.
5
+ * This ensures consistent shape and API-format conversion.
6
+ *
7
+ * Reference: yeaft-unify-core-systems.md §3.1
8
+ */
9
+
10
+ /**
11
+ * @typedef {Object} ToolContext
12
+ * @property {AbortSignal} [signal] — cancellation signal
13
+ * @property {string} [yeaftDir] — Yeaft data directory
14
+ * @property {string} [cwd] — working directory
15
+ * @property {import('../mcp.js').MCPManager} [mcpManager] — MCP manager
16
+ * @property {object} [skillManager] — Skill manager
17
+ * @property {object} [trace] — debug trace
18
+ * @property {object} [config] — engine config
19
+ */
20
+
21
+ /**
22
+ * @typedef {Object} ToolDef
23
+ * @property {string} name — unique tool name (e.g. 'Bash', 'FileRead')
24
+ * @property {string} description — LLM-facing description
25
+ * @property {object} parameters — JSON Schema for input
26
+ * @property {(input: object, ctx?: ToolContext) => Promise<string>} execute — execution function
27
+ * @property {string[]} modes — which modes can use this tool: ['chat', 'work']
28
+ * @property {(input?: object) => boolean} [isConcurrencySafe] — can run in parallel?
29
+ * @property {(input?: object) => boolean} [isReadOnly] — read-only operation?
30
+ * @property {(input?: object) => boolean} [isDestructive] — destructive operation?
31
+ */
32
+
33
+ /**
34
+ * Define a tool with consistent defaults.
35
+ *
36
+ * @param {{
37
+ * name: string,
38
+ * description: string,
39
+ * parameters: object,
40
+ * execute: (input: object, ctx?: ToolContext) => Promise<string>,
41
+ * modes?: string[],
42
+ * isConcurrencySafe?: (input?: object) => boolean,
43
+ * isReadOnly?: (input?: object) => boolean,
44
+ * isDestructive?: (input?: object) => boolean,
45
+ * }} def
46
+ * @returns {ToolDef}
47
+ */
48
+ export function defineTool({
49
+ name,
50
+ description,
51
+ parameters,
52
+ execute,
53
+ modes = ['chat', 'work'],
54
+ isConcurrencySafe = () => false,
55
+ isReadOnly = () => false,
56
+ isDestructive = () => false,
57
+ }) {
58
+ if (!name) throw new Error('Tool must have a name');
59
+ if (!execute) throw new Error(`Tool "${name}" must have an execute function`);
60
+
61
+ return {
62
+ name,
63
+ description: description || `Tool: ${name}`,
64
+ parameters: parameters || { type: 'object', properties: {} },
65
+ execute,
66
+ modes,
67
+ isConcurrencySafe,
68
+ isReadOnly,
69
+ isDestructive,
70
+ };
71
+ }