@yeaft/webchat-agent 0.1.408 → 0.1.410
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.
- package/package.json +1 -1
- package/unify/cli.js +214 -16
- package/unify/config.js +13 -0
- package/unify/conversation/persist.js +436 -0
- package/unify/conversation/search.js +65 -0
- package/unify/engine.js +210 -18
- package/unify/index.js +18 -0
- package/unify/mcp.js +433 -0
- package/unify/memory/consolidate.js +187 -0
- package/unify/memory/dream-prompt.js +272 -0
- package/unify/memory/dream.js +468 -0
- package/unify/memory/extract.js +97 -0
- package/unify/memory/recall.js +243 -0
- package/unify/memory/scan.js +273 -0
- package/unify/memory/store.js +507 -0
- package/unify/memory/types.js +139 -0
- package/unify/prompts.js +51 -3
- package/unify/skills.js +315 -0
- package/unify/stop-hooks.js +146 -0
- package/unify/tools/enter-worktree.js +97 -0
- package/unify/tools/exit-worktree.js +131 -0
- package/unify/tools/mcp-tools.js +133 -0
- package/unify/tools/registry.js +146 -0
- package/unify/tools/skill.js +107 -0
- package/unify/tools/types.js +71 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* exit-worktree.js — Remove or keep a git worktree
|
|
3
|
+
*
|
|
4
|
+
* Exits and optionally removes a worktree created by EnterWorktree.
|
|
5
|
+
* Can keep the worktree (preserve branch) or remove it cleanly.
|
|
6
|
+
*
|
|
7
|
+
* Reference: yeaft-unify-design.md §8
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { defineTool } from './types.js';
|
|
11
|
+
import { execSync } from 'child_process';
|
|
12
|
+
import { existsSync } from 'fs';
|
|
13
|
+
import { resolve } from 'path';
|
|
14
|
+
|
|
15
|
+
export default defineTool({
|
|
16
|
+
name: 'ExitWorktree',
|
|
17
|
+
description: `Exit a git worktree session.
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
- "keep": Leave the worktree and branch on disk (can return later)
|
|
21
|
+
- "remove": Delete the worktree directory and its branch
|
|
22
|
+
|
|
23
|
+
If removing and there are uncommitted changes, the operation will fail
|
|
24
|
+
unless discard_changes is set to true.`,
|
|
25
|
+
parameters: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
properties: {
|
|
28
|
+
path: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'Path to the worktree to exit (required)',
|
|
31
|
+
},
|
|
32
|
+
action: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
enum: ['keep', 'remove'],
|
|
35
|
+
description: '"keep" leaves the worktree on disk; "remove" deletes it',
|
|
36
|
+
},
|
|
37
|
+
discard_changes: {
|
|
38
|
+
type: 'boolean',
|
|
39
|
+
description: 'Force remove even with uncommitted changes (default: false)',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
required: ['path', 'action'],
|
|
43
|
+
},
|
|
44
|
+
modes: ['work'],
|
|
45
|
+
isDestructive: (input) => input?.action === 'remove',
|
|
46
|
+
async execute(input, ctx) {
|
|
47
|
+
const worktreePath = resolve(input.path);
|
|
48
|
+
const mainCwd = ctx?.cwd || process.cwd();
|
|
49
|
+
|
|
50
|
+
if (!existsSync(worktreePath)) {
|
|
51
|
+
return JSON.stringify({
|
|
52
|
+
error: `Worktree path does not exist: ${worktreePath}`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (input.action === 'keep') {
|
|
57
|
+
return JSON.stringify({
|
|
58
|
+
success: true,
|
|
59
|
+
action: 'keep',
|
|
60
|
+
path: worktreePath,
|
|
61
|
+
message: `Worktree at ${worktreePath} kept on disk. Branch preserved.`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// action === 'remove'
|
|
66
|
+
try {
|
|
67
|
+
// Check for uncommitted changes
|
|
68
|
+
if (!input.discard_changes) {
|
|
69
|
+
try {
|
|
70
|
+
const status = execSync('git status --porcelain', {
|
|
71
|
+
cwd: worktreePath,
|
|
72
|
+
encoding: 'utf8',
|
|
73
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
74
|
+
}).trim();
|
|
75
|
+
|
|
76
|
+
if (status) {
|
|
77
|
+
return JSON.stringify({
|
|
78
|
+
error: 'Worktree has uncommitted changes. Set discard_changes=true to force remove.',
|
|
79
|
+
uncommittedFiles: status.split('\n').slice(0, 10),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
// If we can't check status, proceed with caution
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Get branch name before removal
|
|
88
|
+
let branchName = null;
|
|
89
|
+
try {
|
|
90
|
+
branchName = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
91
|
+
cwd: worktreePath,
|
|
92
|
+
encoding: 'utf8',
|
|
93
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
94
|
+
}).trim();
|
|
95
|
+
} catch {
|
|
96
|
+
// ignore
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Remove worktree
|
|
100
|
+
const forceFlag = input.discard_changes ? ' --force' : '';
|
|
101
|
+
execSync(`git worktree remove "${worktreePath}"${forceFlag}`, {
|
|
102
|
+
cwd: mainCwd,
|
|
103
|
+
stdio: 'pipe',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Remove the branch if it was a yeaft worktree branch
|
|
107
|
+
if (branchName && branchName.startsWith('yeaft-wt/')) {
|
|
108
|
+
try {
|
|
109
|
+
execSync(`git branch -D "${branchName}"`, {
|
|
110
|
+
cwd: mainCwd,
|
|
111
|
+
stdio: 'pipe',
|
|
112
|
+
});
|
|
113
|
+
} catch {
|
|
114
|
+
// Branch might already be deleted or not exist
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return JSON.stringify({
|
|
119
|
+
success: true,
|
|
120
|
+
action: 'remove',
|
|
121
|
+
path: worktreePath,
|
|
122
|
+
branch: branchName,
|
|
123
|
+
message: `Worktree removed: ${worktreePath}${branchName ? `, branch ${branchName} deleted` : ''}`,
|
|
124
|
+
});
|
|
125
|
+
} catch (err) {
|
|
126
|
+
return JSON.stringify({
|
|
127
|
+
error: `Failed to remove worktree: ${err.message}`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
});
|
|
@@ -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
|
+
}
|